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 com.android.internal.R; 20 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.content.DialogInterface.OnDismissListener; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.res.Resources; 30 import android.media.AudioManager; 31 import android.media.AudioService; 32 import android.media.AudioSystem; 33 import android.media.RingtoneManager; 34 import android.media.ToneGenerator; 35 import android.net.Uri; 36 import android.os.Handler; 37 import android.os.Message; 38 import android.os.Vibrator; 39 import android.util.Log; 40 import android.view.WindowManager.LayoutParams; 41 import android.widget.ImageView; 42 import android.widget.SeekBar; 43 import android.widget.SeekBar.OnSeekBarChangeListener; 44 45 import 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 */ 58 public 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 StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show)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 WarningDialogReceiver(Context context, Dialog dialog)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 onReceive(Context context, Intent intent)232 public void onReceive(Context context, Intent intent) { 233 mDialog.cancel(); 234 synchronized (sConfirmSafeVolumeLock) { 235 sConfirmSafeVolumeDialog = null; 236 } 237 } 238 onDismiss(DialogInterface unused)239 public void onDismiss(DialogInterface unused) { 240 mContext.unregisterReceiver(this); 241 synchronized (sConfirmSafeVolumeLock) { 242 sConfirmSafeVolumeDialog = null; 243 } 244 } 245 } 246 247 VolumePanel(final Context context, AudioService volumeService)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 lp.privateFlags |= LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR; 306 window.setAttributes(lp); 307 window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL 308 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 309 310 mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; 311 mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); 312 313 mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); 314 mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume; 315 // If we don't want to show multiple volumes, hide the settings button and divider 316 if (!mShowCombinedVolumes) { 317 mMoreButton.setVisibility(View.GONE); 318 mDivider.setVisibility(View.GONE); 319 } else { 320 mMoreButton.setOnClickListener(this); 321 } 322 323 boolean masterVolumeOnly = context.getResources().getBoolean( 324 com.android.internal.R.bool.config_useMasterVolume); 325 boolean masterVolumeKeySounds = mContext.getResources().getBoolean( 326 com.android.internal.R.bool.config_useVolumeKeySounds); 327 328 mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; 329 330 listenToRingerMode(); 331 } 332 listenToRingerMode()333 private void listenToRingerMode() { 334 final IntentFilter filter = new IntentFilter(); 335 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 336 mContext.registerReceiver(new BroadcastReceiver() { 337 338 public void onReceive(Context context, Intent intent) { 339 final String action = intent.getAction(); 340 341 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { 342 removeMessages(MSG_RINGER_MODE_CHANGED); 343 sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED)); 344 } 345 } 346 }, filter); 347 } 348 isMuted(int streamType)349 private boolean isMuted(int streamType) { 350 if (streamType == STREAM_MASTER) { 351 return mAudioManager.isMasterMute(); 352 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 353 return (mAudioService.getRemoteStreamVolume() <= 0); 354 } else { 355 return mAudioManager.isStreamMute(streamType); 356 } 357 } 358 getStreamMaxVolume(int streamType)359 private int getStreamMaxVolume(int streamType) { 360 if (streamType == STREAM_MASTER) { 361 return mAudioManager.getMasterMaxVolume(); 362 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 363 return mAudioService.getRemoteStreamMaxVolume(); 364 } else { 365 return mAudioManager.getStreamMaxVolume(streamType); 366 } 367 } 368 getStreamVolume(int streamType)369 private int getStreamVolume(int streamType) { 370 if (streamType == STREAM_MASTER) { 371 return mAudioManager.getMasterVolume(); 372 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 373 return mAudioService.getRemoteStreamVolume(); 374 } else { 375 return mAudioManager.getStreamVolume(streamType); 376 } 377 } 378 setStreamVolume(int streamType, int index, int flags)379 private void setStreamVolume(int streamType, int index, int flags) { 380 if (streamType == STREAM_MASTER) { 381 mAudioManager.setMasterVolume(index, flags); 382 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 383 mAudioService.setRemoteStreamVolume(index); 384 } else { 385 mAudioManager.setStreamVolume(streamType, index, flags); 386 } 387 } 388 createSliders()389 private void createSliders() { 390 LayoutInflater inflater = (LayoutInflater) mContext 391 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 392 mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length); 393 Resources res = mContext.getResources(); 394 for (int i = 0; i < STREAMS.length; i++) { 395 StreamResources streamRes = STREAMS[i]; 396 int streamType = streamRes.streamType; 397 if (mVoiceCapable && streamRes == StreamResources.NotificationStream) { 398 streamRes = StreamResources.RingerStream; 399 } 400 StreamControl sc = new StreamControl(); 401 sc.streamType = streamType; 402 sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null); 403 sc.group.setTag(sc); 404 sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon); 405 sc.icon.setTag(sc); 406 sc.icon.setContentDescription(res.getString(streamRes.descRes)); 407 sc.iconRes = streamRes.iconRes; 408 sc.iconMuteRes = streamRes.iconMuteRes; 409 sc.icon.setImageResource(sc.iconRes); 410 sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar); 411 int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || 412 streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; 413 sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); 414 sc.seekbarView.setOnSeekBarChangeListener(this); 415 sc.seekbarView.setTag(sc); 416 mStreamControls.put(streamType, sc); 417 } 418 } 419 reorderSliders(int activeStreamType)420 private void reorderSliders(int activeStreamType) { 421 mSliderGroup.removeAllViews(); 422 423 StreamControl active = mStreamControls.get(activeStreamType); 424 if (active == null) { 425 Log.e("VolumePanel", "Missing stream type! - " + activeStreamType); 426 mActiveStreamType = -1; 427 } else { 428 mSliderGroup.addView(active.group); 429 mActiveStreamType = activeStreamType; 430 active.group.setVisibility(View.VISIBLE); 431 updateSlider(active); 432 } 433 434 addOtherVolumes(); 435 } 436 addOtherVolumes()437 private void addOtherVolumes() { 438 if (!mShowCombinedVolumes) return; 439 440 for (int i = 0; i < STREAMS.length; i++) { 441 // Skip the phone specific ones and the active one 442 final int streamType = STREAMS[i].streamType; 443 if (!STREAMS[i].show || streamType == mActiveStreamType) { 444 continue; 445 } 446 StreamControl sc = mStreamControls.get(streamType); 447 mSliderGroup.addView(sc.group); 448 updateSlider(sc); 449 } 450 } 451 452 /** Update the mute and progress state of a slider */ updateSlider(StreamControl sc)453 private void updateSlider(StreamControl sc) { 454 sc.seekbarView.setProgress(getStreamVolume(sc.streamType)); 455 final boolean muted = isMuted(sc.streamType); 456 sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes); 457 if (sc.streamType == AudioManager.STREAM_RING && 458 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { 459 sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); 460 } 461 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { 462 // never disable touch interactions for remote playback, the muting is not tied to 463 // the state of the phone. 464 sc.seekbarView.setEnabled(true); 465 } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) { 466 sc.seekbarView.setEnabled(false); 467 } else { 468 sc.seekbarView.setEnabled(true); 469 } 470 } 471 isExpanded()472 private boolean isExpanded() { 473 return mMoreButton.getVisibility() != View.VISIBLE; 474 } 475 expand()476 private void expand() { 477 final int count = mSliderGroup.getChildCount(); 478 for (int i = 0; i < count; i++) { 479 mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE); 480 } 481 mMoreButton.setVisibility(View.INVISIBLE); 482 mDivider.setVisibility(View.INVISIBLE); 483 } 484 collapse()485 private void collapse() { 486 mMoreButton.setVisibility(View.VISIBLE); 487 mDivider.setVisibility(View.VISIBLE); 488 final int count = mSliderGroup.getChildCount(); 489 for (int i = 1; i < count; i++) { 490 mSliderGroup.getChildAt(i).setVisibility(View.GONE); 491 } 492 } 493 updateStates()494 private void updateStates() { 495 final int count = mSliderGroup.getChildCount(); 496 for (int i = 0; i < count; i++) { 497 StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag(); 498 updateSlider(sc); 499 } 500 } 501 postVolumeChanged(int streamType, int flags)502 public void postVolumeChanged(int streamType, int flags) { 503 if (hasMessages(MSG_VOLUME_CHANGED)) return; 504 synchronized (this) { 505 if (mStreamControls == null) { 506 createSliders(); 507 } 508 } 509 removeMessages(MSG_FREE_RESOURCES); 510 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); 511 } 512 postRemoteVolumeChanged(int streamType, int flags)513 public void postRemoteVolumeChanged(int streamType, int flags) { 514 if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; 515 synchronized (this) { 516 if (mStreamControls == null) { 517 createSliders(); 518 } 519 } 520 removeMessages(MSG_FREE_RESOURCES); 521 obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget(); 522 } 523 postRemoteSliderVisibility(boolean visible)524 public void postRemoteSliderVisibility(boolean visible) { 525 obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, 526 AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); 527 } 528 529 /** 530 * Called by AudioService when it has received new remote playback information that 531 * would affect the VolumePanel display (mainly volumes). The difference with 532 * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message 533 * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being 534 * displayed. 535 * This special code path is due to the fact that remote volume updates arrive to AudioService 536 * asynchronously. So after AudioService has sent the volume update (which should be treated 537 * as a request to update the volume), the application will likely set a new volume. If the UI 538 * is still up, we need to refresh the display to show this new value. 539 */ postHasNewRemotePlaybackInfo()540 public void postHasNewRemotePlaybackInfo() { 541 if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; 542 // don't create or prevent resources to be freed, if they disappear, this update came too 543 // late and shouldn't warrant the panel to be displayed longer 544 obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); 545 } 546 postMasterVolumeChanged(int flags)547 public void postMasterVolumeChanged(int flags) { 548 postVolumeChanged(STREAM_MASTER, flags); 549 } 550 postMuteChanged(int streamType, int flags)551 public void postMuteChanged(int streamType, int flags) { 552 if (hasMessages(MSG_VOLUME_CHANGED)) return; 553 synchronized (this) { 554 if (mStreamControls == null) { 555 createSliders(); 556 } 557 } 558 removeMessages(MSG_FREE_RESOURCES); 559 obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); 560 } 561 postMasterMuteChanged(int flags)562 public void postMasterMuteChanged(int flags) { 563 postMuteChanged(STREAM_MASTER, flags); 564 } 565 postDisplaySafeVolumeWarning()566 public void postDisplaySafeVolumeWarning() { 567 if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; 568 obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget(); 569 } 570 571 /** 572 * Override this if you have other work to do when the volume changes (for 573 * example, vibrating, playing a sound, etc.). Make sure to call through to 574 * the superclass implementation. 575 */ onVolumeChanged(int streamType, int flags)576 protected void onVolumeChanged(int streamType, int flags) { 577 578 if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); 579 580 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 581 synchronized (this) { 582 if (mActiveStreamType != streamType) { 583 reorderSliders(streamType); 584 } 585 onShowVolumeChanged(streamType, flags); 586 } 587 } 588 589 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 590 removeMessages(MSG_PLAY_SOUND); 591 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 592 } 593 594 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 595 removeMessages(MSG_PLAY_SOUND); 596 removeMessages(MSG_VIBRATE); 597 onStopSounds(); 598 } 599 600 removeMessages(MSG_FREE_RESOURCES); 601 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 602 603 resetTimeout(); 604 } 605 onMuteChanged(int streamType, int flags)606 protected void onMuteChanged(int streamType, int flags) { 607 608 if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); 609 610 StreamControl sc = mStreamControls.get(streamType); 611 if (sc != null) { 612 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes); 613 } 614 615 onVolumeChanged(streamType, flags); 616 } 617 onShowVolumeChanged(int streamType, int flags)618 protected void onShowVolumeChanged(int streamType, int flags) { 619 int index = getStreamVolume(streamType); 620 621 mRingIsSilent = false; 622 623 if (LOGD) { 624 Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType 625 + ", flags: " + flags + "), index: " + index); 626 } 627 628 // get max volume for progress bar 629 630 int max = getStreamMaxVolume(streamType); 631 632 switch (streamType) { 633 634 case AudioManager.STREAM_RING: { 635 // setRingerIcon(); 636 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 637 mContext, RingtoneManager.TYPE_RINGTONE); 638 if (ringuri == null) { 639 mRingIsSilent = true; 640 } 641 break; 642 } 643 644 case AudioManager.STREAM_MUSIC: { 645 // Special case for when Bluetooth is active for music 646 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) & 647 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | 648 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | 649 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { 650 setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute); 651 } else { 652 setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute); 653 } 654 break; 655 } 656 657 case AudioManager.STREAM_VOICE_CALL: { 658 /* 659 * For in-call voice call volume, there is no inaudible volume. 660 * Rescale the UI control so the progress bar doesn't go all 661 * the way to zero and don't show the mute icon. 662 */ 663 index++; 664 max++; 665 break; 666 } 667 668 case AudioManager.STREAM_ALARM: { 669 break; 670 } 671 672 case AudioManager.STREAM_NOTIFICATION: { 673 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 674 mContext, RingtoneManager.TYPE_NOTIFICATION); 675 if (ringuri == null) { 676 mRingIsSilent = true; 677 } 678 break; 679 } 680 681 case AudioManager.STREAM_BLUETOOTH_SCO: { 682 /* 683 * For in-call voice call volume, there is no inaudible volume. 684 * Rescale the UI control so the progress bar doesn't go all 685 * the way to zero and don't show the mute icon. 686 */ 687 index++; 688 max++; 689 break; 690 } 691 692 case AudioService.STREAM_REMOTE_MUSIC: { 693 if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); } 694 break; 695 } 696 } 697 698 StreamControl sc = mStreamControls.get(streamType); 699 if (sc != null) { 700 if (sc.seekbarView.getMax() != max) { 701 sc.seekbarView.setMax(max); 702 } 703 704 sc.seekbarView.setProgress(index); 705 if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) || 706 (streamType != mAudioManager.getMasterStreamType() && 707 streamType != AudioService.STREAM_REMOTE_MUSIC && 708 isMuted(streamType))) { 709 sc.seekbarView.setEnabled(false); 710 } else { 711 sc.seekbarView.setEnabled(true); 712 } 713 } 714 715 if (!mDialog.isShowing()) { 716 int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType; 717 // when the stream is for remote playback, use -1 to reset the stream type evaluation 718 mAudioManager.forceVolumeControlStream(stream); 719 mDialog.setContentView(mView); 720 // Showing dialog - use collapsed state 721 if (mShowCombinedVolumes) { 722 collapse(); 723 } 724 mDialog.show(); 725 } 726 727 // Do a little vibrate if applicable (only when going into vibrate mode) 728 if ((streamType != AudioService.STREAM_REMOTE_MUSIC) && 729 ((flags & AudioManager.FLAG_VIBRATE) != 0) && 730 mAudioService.isStreamAffectedByRingerMode(streamType) && 731 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { 732 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); 733 } 734 } 735 onPlaySound(int streamType, int flags)736 protected void onPlaySound(int streamType, int flags) { 737 738 if (hasMessages(MSG_STOP_SOUNDS)) { 739 removeMessages(MSG_STOP_SOUNDS); 740 // Force stop right now 741 onStopSounds(); 742 } 743 744 synchronized (this) { 745 ToneGenerator toneGen = getOrCreateToneGenerator(streamType); 746 if (toneGen != null) { 747 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); 748 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); 749 } 750 } 751 } 752 onStopSounds()753 protected void onStopSounds() { 754 755 synchronized (this) { 756 int numStreamTypes = AudioSystem.getNumStreamTypes(); 757 for (int i = numStreamTypes - 1; i >= 0; i--) { 758 ToneGenerator toneGen = mToneGenerators[i]; 759 if (toneGen != null) { 760 toneGen.stopTone(); 761 } 762 } 763 } 764 } 765 onVibrate()766 protected void onVibrate() { 767 768 // Make sure we ended up in vibrate ringer mode 769 if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { 770 return; 771 } 772 773 mVibrator.vibrate(VIBRATE_DURATION); 774 } 775 onRemoteVolumeChanged(int streamType, int flags)776 protected void onRemoteVolumeChanged(int streamType, int flags) { 777 // streamType is the real stream type being affected, but for the UI sliders, we 778 // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real 779 // stream type. 780 if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); 781 782 if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) { 783 synchronized (this) { 784 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) { 785 reorderSliders(AudioService.STREAM_REMOTE_MUSIC); 786 } 787 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags); 788 } 789 } else { 790 if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); 791 } 792 793 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 794 removeMessages(MSG_PLAY_SOUND); 795 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 796 } 797 798 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 799 removeMessages(MSG_PLAY_SOUND); 800 removeMessages(MSG_VIBRATE); 801 onStopSounds(); 802 } 803 804 removeMessages(MSG_FREE_RESOURCES); 805 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 806 807 resetTimeout(); 808 } 809 onRemoteVolumeUpdateIfShown()810 protected void onRemoteVolumeUpdateIfShown() { 811 if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()"); 812 if (mDialog.isShowing() 813 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC) 814 && (mStreamControls != null)) { 815 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0); 816 } 817 } 818 819 820 /** 821 * Handler for MSG_SLIDER_VISIBILITY_CHANGED 822 * Hide or show a slider 823 * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER, 824 * or AudioService.STREAM_REMOTE_MUSIC 825 * @param visible 826 */ onSliderVisibilityChanged(int streamType, int visible)827 synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { 828 if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); 829 boolean isVisible = (visible == 1); 830 for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { 831 StreamResources streamRes = STREAMS[i]; 832 if (streamRes.streamType == streamType) { 833 streamRes.show = isVisible; 834 if (!isVisible && (mActiveStreamType == streamType)) { 835 mActiveStreamType = -1; 836 } 837 break; 838 } 839 } 840 } 841 onDisplaySafeVolumeWarning()842 protected void onDisplaySafeVolumeWarning() { 843 synchronized (sConfirmSafeVolumeLock) { 844 if (sConfirmSafeVolumeDialog != null) { 845 return; 846 } 847 sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) 848 .setMessage(com.android.internal.R.string.safe_media_volume_warning) 849 .setPositiveButton(com.android.internal.R.string.yes, 850 new DialogInterface.OnClickListener() { 851 public void onClick(DialogInterface dialog, int which) { 852 mAudioService.disableSafeMediaVolume(); 853 } 854 }) 855 .setNegativeButton(com.android.internal.R.string.no, null) 856 .setIconAttribute(android.R.attr.alertDialogIcon) 857 .create(); 858 final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, 859 sConfirmSafeVolumeDialog); 860 861 sConfirmSafeVolumeDialog.setOnDismissListener(warning); 862 sConfirmSafeVolumeDialog.getWindow().setType( 863 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 864 sConfirmSafeVolumeDialog.show(); 865 } 866 } 867 868 /** 869 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. 870 */ getOrCreateToneGenerator(int streamType)871 private ToneGenerator getOrCreateToneGenerator(int streamType) { 872 if (streamType == STREAM_MASTER) { 873 // For devices that use the master volume setting only but still want to 874 // play a volume-changed tone, direct the master volume pseudostream to 875 // the system stream's tone generator. 876 if (mPlayMasterStreamTones) { 877 streamType = AudioManager.STREAM_SYSTEM; 878 } else { 879 return null; 880 } 881 } 882 synchronized (this) { 883 if (mToneGenerators[streamType] == null) { 884 try { 885 mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); 886 } catch (RuntimeException e) { 887 if (LOGD) { 888 Log.d(TAG, "ToneGenerator constructor failed with " 889 + "RuntimeException: " + e); 890 } 891 } 892 } 893 return mToneGenerators[streamType]; 894 } 895 } 896 897 898 /** 899 * Switch between icons because Bluetooth music is same as music volume, but with 900 * different icons. 901 */ setMusicIcon(int resId, int resMuteId)902 private void setMusicIcon(int resId, int resMuteId) { 903 StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC); 904 if (sc != null) { 905 sc.iconRes = resId; 906 sc.iconMuteRes = resMuteId; 907 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes); 908 } 909 } 910 onFreeResources()911 protected void onFreeResources() { 912 synchronized (this) { 913 for (int i = mToneGenerators.length - 1; i >= 0; i--) { 914 if (mToneGenerators[i] != null) { 915 mToneGenerators[i].release(); 916 } 917 mToneGenerators[i] = null; 918 } 919 } 920 } 921 922 @Override handleMessage(Message msg)923 public void handleMessage(Message msg) { 924 switch (msg.what) { 925 926 case MSG_VOLUME_CHANGED: { 927 onVolumeChanged(msg.arg1, msg.arg2); 928 break; 929 } 930 931 case MSG_MUTE_CHANGED: { 932 onMuteChanged(msg.arg1, msg.arg2); 933 break; 934 } 935 936 case MSG_FREE_RESOURCES: { 937 onFreeResources(); 938 break; 939 } 940 941 case MSG_STOP_SOUNDS: { 942 onStopSounds(); 943 break; 944 } 945 946 case MSG_PLAY_SOUND: { 947 onPlaySound(msg.arg1, msg.arg2); 948 break; 949 } 950 951 case MSG_VIBRATE: { 952 onVibrate(); 953 break; 954 } 955 956 case MSG_TIMEOUT: { 957 if (mDialog.isShowing()) { 958 mDialog.dismiss(); 959 mActiveStreamType = -1; 960 } 961 break; 962 } 963 case MSG_RINGER_MODE_CHANGED: { 964 if (mDialog.isShowing()) { 965 updateStates(); 966 } 967 break; 968 } 969 970 case MSG_REMOTE_VOLUME_CHANGED: { 971 onRemoteVolumeChanged(msg.arg1, msg.arg2); 972 break; 973 } 974 975 case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: 976 onRemoteVolumeUpdateIfShown(); 977 break; 978 979 case MSG_SLIDER_VISIBILITY_CHANGED: 980 onSliderVisibilityChanged(msg.arg1, msg.arg2); 981 break; 982 983 case MSG_DISPLAY_SAFE_VOLUME_WARNING: 984 onDisplaySafeVolumeWarning(); 985 break; 986 } 987 } 988 resetTimeout()989 private void resetTimeout() { 990 removeMessages(MSG_TIMEOUT); 991 sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY); 992 } 993 forceTimeout()994 private void forceTimeout() { 995 removeMessages(MSG_TIMEOUT); 996 sendMessage(obtainMessage(MSG_TIMEOUT)); 997 } 998 onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)999 public void onProgressChanged(SeekBar seekBar, int progress, 1000 boolean fromUser) { 1001 final Object tag = seekBar.getTag(); 1002 if (fromUser && tag instanceof StreamControl) { 1003 StreamControl sc = (StreamControl) tag; 1004 if (getStreamVolume(sc.streamType) != progress) { 1005 setStreamVolume(sc.streamType, progress, 0); 1006 } 1007 } 1008 resetTimeout(); 1009 } 1010 onStartTrackingTouch(SeekBar seekBar)1011 public void onStartTrackingTouch(SeekBar seekBar) { 1012 } 1013 onStopTrackingTouch(SeekBar seekBar)1014 public void onStopTrackingTouch(SeekBar seekBar) { 1015 final Object tag = seekBar.getTag(); 1016 if (tag instanceof StreamControl) { 1017 StreamControl sc = (StreamControl) tag; 1018 // because remote volume updates are asynchronous, AudioService might have received 1019 // a new remote volume value since the finger adjusted the slider. So when the 1020 // progress of the slider isn't being tracked anymore, adjust the slider to the last 1021 // "published" remote volume value, so the UI reflects the actual volume. 1022 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { 1023 seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC)); 1024 } 1025 } 1026 } 1027 onClick(View v)1028 public void onClick(View v) { 1029 if (v == mMoreButton) { 1030 expand(); 1031 } 1032 resetTimeout(); 1033 } 1034 } 1035