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