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