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