VolumePanel.java revision cfe9fb67949100c864c7b0b913d10a7cfd0fc141
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.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.app.AlertDialog; 23import android.app.Dialog; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.DialogInterface; 28import android.content.DialogInterface.OnDismissListener; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.pm.PackageManager; 32import android.content.pm.ServiceInfo; 33import android.content.res.Configuration; 34import android.content.res.Resources; 35import android.content.res.TypedArray; 36import android.graphics.PixelFormat; 37import android.graphics.drawable.ColorDrawable; 38import android.media.AudioAttributes; 39import android.media.AudioManager; 40import android.media.AudioService; 41import android.media.AudioSystem; 42import android.media.RingtoneManager; 43import android.media.ToneGenerator; 44import android.media.VolumeProvider; 45import android.media.session.MediaController; 46import android.media.session.MediaController.PlaybackInfo; 47import android.net.Uri; 48import android.os.AsyncTask; 49import android.os.Bundle; 50import android.os.Debug; 51import android.os.Handler; 52import android.os.Message; 53import android.os.Vibrator; 54import android.util.Log; 55import android.util.SparseArray; 56import android.view.KeyEvent; 57import android.view.LayoutInflater; 58import android.view.MotionEvent; 59import android.view.View; 60import android.view.View.OnClickListener; 61import android.view.ViewGroup; 62import android.view.Window; 63import android.view.WindowManager; 64import android.view.WindowManager.LayoutParams; 65import android.view.accessibility.AccessibilityEvent; 66import android.view.accessibility.AccessibilityManager; 67import android.view.animation.AnimationUtils; 68import android.view.animation.Interpolator; 69import android.widget.ImageView; 70import android.widget.SeekBar; 71import android.widget.SeekBar.OnSeekBarChangeListener; 72import android.widget.TextView; 73 74import com.android.internal.R; 75import com.android.systemui.DemoMode; 76import com.android.systemui.statusbar.phone.SystemUIDialog; 77import com.android.systemui.statusbar.policy.ZenModeController; 78 79import java.io.FileDescriptor; 80import java.io.PrintWriter; 81 82/** 83 * Handles the user interface for the volume keys. 84 * 85 * @hide 86 */ 87public class VolumePanel extends Handler implements DemoMode { 88 private static final String TAG = "VolumePanel"; 89 private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); 90 91 private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY; 92 93 /** 94 * The delay before vibrating. This small period exists so if the user is 95 * moving to silent mode, it will not emit a short vibrate (it normally 96 * would since vibrate is between normal mode and silent mode using hardware 97 * keys). 98 */ 99 public static final int VIBRATE_DELAY = 300; 100 101 private static final int VIBRATE_DURATION = 300; 102 private static final int BEEP_DURATION = 150; 103 private static final int MAX_VOLUME = 100; 104 private static final int FREE_DELAY = 10000; 105 private static final int TIMEOUT_DELAY = 3000; 106 private static final int TIMEOUT_DELAY_SHORT = 1500; 107 private static final int TIMEOUT_DELAY_COLLAPSED = 4500; 108 private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000; 109 private static final int TIMEOUT_DELAY_EXPANDED = 10000; 110 111 private static final int MSG_VOLUME_CHANGED = 0; 112 private static final int MSG_FREE_RESOURCES = 1; 113 private static final int MSG_PLAY_SOUND = 2; 114 private static final int MSG_STOP_SOUNDS = 3; 115 private static final int MSG_VIBRATE = 4; 116 private static final int MSG_TIMEOUT = 5; 117 private static final int MSG_RINGER_MODE_CHANGED = 6; 118 private static final int MSG_MUTE_CHANGED = 7; 119 private static final int MSG_REMOTE_VOLUME_CHANGED = 8; 120 private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; 121 private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; 122 private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; 123 private static final int MSG_LAYOUT_DIRECTION = 12; 124 private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13; 125 private static final int MSG_USER_ACTIVITY = 14; 126 private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15; 127 private static final int MSG_ZEN_MODE_CHANGED = 16; 128 private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 17; 129 130 // Pseudo stream type for master volume 131 private static final int STREAM_MASTER = -100; 132 // Pseudo stream type for remote volume 133 private static final int STREAM_REMOTE_MUSIC = -200; 134 135 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 136 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 137 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 138 .build(); 139 140 private static final int IC_AUDIO_VOL = com.android.systemui.R.drawable.ic_audio_vol; 141 private static final int IC_AUDIO_VOL_MUTE = com.android.systemui.R.drawable.ic_audio_vol_mute; 142 private static final int IC_AUDIO_BT = com.android.systemui.R.drawable.ic_audio_bt; 143 private static final int IC_AUDIO_BT_MUTE = com.android.systemui.R.drawable.ic_audio_bt_mute; 144 145 private final String mTag; 146 protected final Context mContext; 147 private final AudioManager mAudioManager; 148 private final ZenModeController mZenController; 149 private boolean mRingIsSilent; 150 private boolean mVoiceCapable; 151 private boolean mZenModeAvailable; 152 private boolean mZenPanelExpanded; 153 private int mTimeoutDelay = TIMEOUT_DELAY; 154 private float mDisabledAlpha; 155 private int mLastRingerMode = AudioManager.RINGER_MODE_NORMAL; 156 private int mLastRingerProgress = 0; 157 private int mDemoIcon; 158 159 // True if we want to play tones on the system stream when the master stream is specified. 160 private final boolean mPlayMasterStreamTones; 161 162 163 /** Volume panel content view */ 164 private final View mView; 165 /** Dialog hosting the panel */ 166 private final Dialog mDialog; 167 168 /** The visible portion of the volume overlay */ 169 private final ViewGroup mPanel; 170 /** Contains the slider and its touchable icons */ 171 private final ViewGroup mSliderPanel; 172 /** The zen mode configuration panel view */ 173 private ZenModePanel mZenPanel; 174 /** The component currently suppressing notification stream effects */ 175 private ComponentName mNotificationEffectsSuppressor; 176 177 private Callback mCallback; 178 179 /** Currently active stream that shows up at the top of the list of sliders */ 180 private int mActiveStreamType = -1; 181 /** All the slider controls mapped by stream type */ 182 private SparseArray<StreamControl> mStreamControls; 183 private final AccessibilityManager mAccessibilityManager; 184 private final SecondaryIconTransition mSecondaryIconTransition; 185 private final IconPulser mIconPulser; 186 187 private enum StreamResources { 188 BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO, 189 R.string.volume_icon_description_bluetooth, 190 IC_AUDIO_BT, 191 IC_AUDIO_BT_MUTE, 192 false), 193 RingerStream(AudioManager.STREAM_RING, 194 R.string.volume_icon_description_ringer, 195 com.android.systemui.R.drawable.ic_ringer_audible, 196 com.android.systemui.R.drawable.ic_ringer_mute, 197 false), 198 VoiceStream(AudioManager.STREAM_VOICE_CALL, 199 R.string.volume_icon_description_incall, 200 com.android.systemui.R.drawable.ic_audio_phone, 201 com.android.systemui.R.drawable.ic_audio_phone, 202 false), 203 AlarmStream(AudioManager.STREAM_ALARM, 204 R.string.volume_alarm, 205 com.android.systemui.R.drawable.ic_audio_alarm, 206 com.android.systemui.R.drawable.ic_audio_alarm_mute, 207 false), 208 MediaStream(AudioManager.STREAM_MUSIC, 209 R.string.volume_icon_description_media, 210 IC_AUDIO_VOL, 211 IC_AUDIO_VOL_MUTE, 212 true), 213 NotificationStream(AudioManager.STREAM_NOTIFICATION, 214 R.string.volume_icon_description_notification, 215 com.android.systemui.R.drawable.ic_ringer_audible, 216 com.android.systemui.R.drawable.ic_ringer_mute, 217 true), 218 // for now, use media resources for master volume 219 MasterStream(STREAM_MASTER, 220 R.string.volume_icon_description_media, //FIXME should have its own description 221 IC_AUDIO_VOL, 222 IC_AUDIO_VOL_MUTE, 223 false), 224 RemoteStream(STREAM_REMOTE_MUSIC, 225 R.string.volume_icon_description_media, //FIXME should have its own description 226 com.android.systemui.R.drawable.ic_audio_remote, 227 com.android.systemui.R.drawable.ic_audio_remote, 228 false);// will be dynamically updated 229 230 int streamType; 231 int descRes; 232 int iconRes; 233 int iconMuteRes; 234 // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested 235 boolean show; 236 237 StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) { 238 this.streamType = streamType; 239 this.descRes = descRes; 240 this.iconRes = iconRes; 241 this.iconMuteRes = iconMuteRes; 242 this.show = show; 243 } 244 } 245 246 // List of stream types and their order 247 private static final StreamResources[] STREAMS = { 248 StreamResources.BluetoothSCOStream, 249 StreamResources.RingerStream, 250 StreamResources.VoiceStream, 251 StreamResources.MediaStream, 252 StreamResources.NotificationStream, 253 StreamResources.AlarmStream, 254 StreamResources.MasterStream, 255 StreamResources.RemoteStream 256 }; 257 258 /** Object that contains data for each slider */ 259 private class StreamControl { 260 int streamType; 261 MediaController controller; 262 ViewGroup group; 263 ImageView icon; 264 SeekBar seekbarView; 265 TextView suppressorView; 266 View divider; 267 ImageView secondaryIcon; 268 int iconRes; 269 int iconMuteRes; 270 int iconSuppressedRes; 271 } 272 273 // Synchronize when accessing this 274 private ToneGenerator mToneGenerators[]; 275 private Vibrator mVibrator; 276 private boolean mHasVibrator; 277 278 private static AlertDialog sSafetyWarning; 279 private static Object sSafetyWarningLock = new Object(); 280 281 private static class SafetyWarning extends SystemUIDialog 282 implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { 283 private final Context mContext; 284 private final VolumePanel mVolumePanel; 285 private final AudioManager mAudioManager; 286 287 private boolean mNewVolumeUp; 288 289 SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) { 290 super(context); 291 mContext = context; 292 mVolumePanel = volumePanel; 293 mAudioManager = audioManager; 294 295 setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning)); 296 setButton(DialogInterface.BUTTON_POSITIVE, 297 mContext.getString(com.android.internal.R.string.yes), this); 298 setButton(DialogInterface.BUTTON_NEGATIVE, 299 mContext.getString(com.android.internal.R.string.no), (OnClickListener) null); 300 setOnDismissListener(this); 301 302 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 303 context.registerReceiver(mReceiver, filter); 304 } 305 306 @Override 307 public boolean onKeyDown(int keyCode, KeyEvent event) { 308 if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) { 309 mNewVolumeUp = true; 310 } 311 return super.onKeyDown(keyCode, event); 312 } 313 314 @Override 315 public boolean onKeyUp(int keyCode, KeyEvent event) { 316 if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) { 317 if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP"); 318 mAudioManager.disableSafeMediaVolume(); 319 dismiss(); 320 } 321 return super.onKeyUp(keyCode, event); 322 } 323 324 @Override 325 public void onClick(DialogInterface dialog, int which) { 326 mAudioManager.disableSafeMediaVolume(); 327 } 328 329 @Override 330 public void onDismiss(DialogInterface unused) { 331 mContext.unregisterReceiver(mReceiver); 332 cleanUp(); 333 } 334 335 private void cleanUp() { 336 synchronized (sSafetyWarningLock) { 337 sSafetyWarning = null; 338 } 339 mVolumePanel.forceTimeout(0); 340 mVolumePanel.updateStates(); 341 } 342 343 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 344 @Override 345 public void onReceive(Context context, Intent intent) { 346 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { 347 if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); 348 cancel(); 349 cleanUp(); 350 } 351 } 352 }; 353 } 354 355 public VolumePanel(Context context, ZenModeController zenController) { 356 mTag = String.format("%s.%08x", TAG, hashCode()); 357 mContext = context; 358 mZenController = zenController; 359 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 360 mAccessibilityManager = (AccessibilityManager) context.getSystemService( 361 Context.ACCESSIBILITY_SERVICE); 362 mSecondaryIconTransition = new SecondaryIconTransition(); 363 mIconPulser = new IconPulser(context); 364 365 // For now, only show master volume if master volume is supported 366 final Resources res = context.getResources(); 367 final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume); 368 if (useMasterVolume) { 369 for (int i = 0; i < STREAMS.length; i++) { 370 StreamResources streamRes = STREAMS[i]; 371 streamRes.show = (streamRes.streamType == STREAM_MASTER); 372 } 373 } 374 if (LOGD) Log.d(mTag, "new VolumePanel"); 375 376 mDisabledAlpha = 0.5f; 377 if (mContext.getTheme() != null) { 378 final TypedArray arr = mContext.getTheme().obtainStyledAttributes( 379 new int[] { android.R.attr.disabledAlpha }); 380 mDisabledAlpha = arr.getFloat(0, mDisabledAlpha); 381 arr.recycle(); 382 } 383 384 mDialog = new Dialog(context) { 385 @Override 386 public boolean onTouchEvent(MotionEvent event) { 387 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && 388 sSafetyWarning == null) { 389 forceTimeout(0); 390 return true; 391 } 392 return false; 393 } 394 }; 395 396 final Window window = mDialog.getWindow(); 397 window.requestFeature(Window.FEATURE_NO_TITLE); 398 mDialog.setCanceledOnTouchOutside(true); 399 mDialog.setContentView(com.android.systemui.R.layout.volume_dialog); 400 mDialog.setOnDismissListener(new OnDismissListener() { 401 @Override 402 public void onDismiss(DialogInterface dialog) { 403 mActiveStreamType = -1; 404 mAudioManager.forceVolumeControlStream(mActiveStreamType); 405 setZenPanelVisible(false); 406 mDemoIcon = 0; 407 mSecondaryIconTransition.cancel(); 408 } 409 }); 410 411 mDialog.create(); 412 413 final LayoutParams lp = window.getAttributes(); 414 lp.token = null; 415 lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); 416 lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL; 417 lp.format = PixelFormat.TRANSLUCENT; 418 lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation; 419 lp.setTitle(TAG); 420 window.setAttributes(lp); 421 422 updateWidth(); 423 424 window.setBackgroundDrawable(new ColorDrawable(0x00000000)); 425 window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 426 window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE 427 | LayoutParams.FLAG_NOT_TOUCH_MODAL 428 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 429 | LayoutParams.FLAG_HARDWARE_ACCELERATED); 430 mView = window.findViewById(R.id.content); 431 Interaction.register(mView, new Interaction.Callback() { 432 @Override 433 public void onInteraction() { 434 resetTimeout(); 435 } 436 }); 437 438 mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); 439 mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); 440 mZenPanel = (ZenModePanel) mView.findViewById(com.android.systemui.R.id.zen_mode_panel); 441 initZenModePanel(); 442 443 mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; 444 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 445 mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); 446 mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); 447 448 if (mZenController != null && !useMasterVolume) { 449 mZenModeAvailable = mZenController.isZenAvailable(); 450 mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); 451 mZenController.addCallback(mZenCallback); 452 } 453 454 final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume); 455 final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds); 456 mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; 457 458 registerReceiver(); 459 } 460 461 public void onConfigurationChanged(Configuration newConfig) { 462 updateWidth(); 463 if (mZenPanel != null) { 464 mZenPanel.updateLocale(); 465 } 466 } 467 468 private void updateWidth() { 469 final Resources res = mContext.getResources(); 470 final LayoutParams lp = mDialog.getWindow().getAttributes(); 471 lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.notification_panel_width); 472 lp.gravity = 473 res.getInteger(com.android.systemui.R.integer.notification_panel_layout_gravity); 474 mDialog.getWindow().setAttributes(lp); 475 } 476 477 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 478 pw.println("VolumePanel state:"); 479 pw.print(" mTag="); pw.println(mTag); 480 pw.print(" mRingIsSilent="); pw.println(mRingIsSilent); 481 pw.print(" mVoiceCapable="); pw.println(mVoiceCapable); 482 pw.print(" mHasVibrator="); pw.println(mHasVibrator); 483 pw.print(" mZenModeAvailable="); pw.println(mZenModeAvailable); 484 pw.print(" mZenPanelExpanded="); pw.println(mZenPanelExpanded); 485 pw.print(" mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor); 486 pw.print(" mTimeoutDelay="); pw.println(mTimeoutDelay); 487 pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha); 488 pw.print(" mLastRingerMode="); pw.println(mLastRingerMode); 489 pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress); 490 pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones); 491 pw.print(" isShowing()="); pw.println(isShowing()); 492 pw.print(" mCallback="); pw.println(mCallback); 493 pw.print(" sConfirmSafeVolumeDialog="); 494 pw.println(sSafetyWarning != null ? "<not null>" : null); 495 pw.print(" mActiveStreamType="); pw.println(mActiveStreamType); 496 pw.print(" mStreamControls="); 497 if (mStreamControls == null) { 498 pw.println("null"); 499 } else { 500 final int N = mStreamControls.size(); 501 pw.print("<size "); pw.print(N); pw.println('>'); 502 for (int i = 0; i < N; i++) { 503 final StreamControl sc = mStreamControls.valueAt(i); 504 pw.print(" stream "); pw.print(sc.streamType); pw.print(":"); 505 if (sc.seekbarView != null) { 506 pw.print(" progress="); pw.print(sc.seekbarView.getProgress()); 507 pw.print(" of "); pw.print(sc.seekbarView.getMax()); 508 if (!sc.seekbarView.isEnabled()) pw.print(" (disabled)"); 509 } 510 if (sc.icon != null && sc.icon.isClickable()) pw.print(" (clickable)"); 511 pw.println(); 512 } 513 } 514 } 515 516 private void initZenModePanel() { 517 mZenPanel.init(mZenController); 518 mZenPanel.setCallback(new ZenModePanel.Callback() { 519 @Override 520 public void onMoreSettings() { 521 if (mCallback != null) { 522 mCallback.onZenSettings(); 523 } 524 } 525 526 @Override 527 public void onInteraction() { 528 resetTimeout(); 529 } 530 531 @Override 532 public void onExpanded(boolean expanded) { 533 if (mZenPanelExpanded == expanded) return; 534 mZenPanelExpanded = expanded; 535 updateTimeoutDelay(); 536 resetTimeout(); 537 } 538 }); 539 } 540 541 private void setLayoutDirection(int layoutDirection) { 542 mPanel.setLayoutDirection(layoutDirection); 543 updateStates(); 544 } 545 546 private void registerReceiver() { 547 final IntentFilter filter = new IntentFilter(); 548 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 549 filter.addAction(Intent.ACTION_SCREEN_OFF); 550 mContext.registerReceiver(new BroadcastReceiver() { 551 @Override 552 public void onReceive(Context context, Intent intent) { 553 final String action = intent.getAction(); 554 555 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { 556 removeMessages(MSG_RINGER_MODE_CHANGED); 557 sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED)); 558 } 559 560 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 561 postDismiss(0); 562 } 563 } 564 }, filter); 565 } 566 567 private boolean isMuted(int streamType) { 568 if (streamType == STREAM_MASTER) { 569 return mAudioManager.isMasterMute(); 570 } else if (streamType == STREAM_REMOTE_MUSIC) { 571 // TODO do we need to support a distinct mute property for remote? 572 return false; 573 } else { 574 return mAudioManager.isStreamMute(streamType); 575 } 576 } 577 578 private int getStreamMaxVolume(int streamType) { 579 if (streamType == STREAM_MASTER) { 580 return mAudioManager.getMasterMaxVolume(); 581 } else if (streamType == STREAM_REMOTE_MUSIC) { 582 if (mStreamControls != null) { 583 StreamControl sc = mStreamControls.get(streamType); 584 if (sc != null && sc.controller != null) { 585 PlaybackInfo ai = sc.controller.getPlaybackInfo(); 586 return ai.getMaxVolume(); 587 } 588 } 589 return -1; 590 } else { 591 return mAudioManager.getStreamMaxVolume(streamType); 592 } 593 } 594 595 private int getStreamVolume(int streamType) { 596 if (streamType == STREAM_MASTER) { 597 return mAudioManager.getMasterVolume(); 598 } else if (streamType == STREAM_REMOTE_MUSIC) { 599 if (mStreamControls != null) { 600 StreamControl sc = mStreamControls.get(streamType); 601 if (sc != null && sc.controller != null) { 602 PlaybackInfo ai = sc.controller.getPlaybackInfo(); 603 return ai.getCurrentVolume(); 604 } 605 } 606 return -1; 607 } else { 608 return mAudioManager.getStreamVolume(streamType); 609 } 610 } 611 612 private void setStreamVolume(StreamControl sc, int index, int flags) { 613 if (sc.streamType == STREAM_REMOTE_MUSIC) { 614 if (sc.controller != null) { 615 sc.controller.setVolumeTo(index, flags); 616 } else { 617 Log.w(mTag, "Adjusting remote volume without a controller!"); 618 } 619 } else if (getStreamVolume(sc.streamType) != index) { 620 if (sc.streamType == STREAM_MASTER) { 621 mAudioManager.setMasterVolume(index, flags); 622 } else { 623 mAudioManager.setStreamVolume(sc.streamType, index, flags); 624 } 625 } 626 } 627 628 private void createSliders() { 629 final Resources res = mContext.getResources(); 630 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 631 Context.LAYOUT_INFLATER_SERVICE); 632 633 mStreamControls = new SparseArray<StreamControl>(STREAMS.length); 634 635 final StreamResources notificationStream = StreamResources.NotificationStream; 636 for (int i = 0; i < STREAMS.length; i++) { 637 StreamResources streamRes = STREAMS[i]; 638 639 final int streamType = streamRes.streamType; 640 final boolean isNotification = isNotificationOrRing(streamType); 641 642 final StreamControl sc = new StreamControl(); 643 sc.streamType = streamType; 644 sc.group = (ViewGroup) inflater.inflate( 645 com.android.systemui.R.layout.volume_panel_item, null); 646 sc.group.setTag(sc); 647 sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon); 648 sc.icon.setTag(sc); 649 sc.icon.setContentDescription(res.getString(streamRes.descRes)); 650 sc.iconRes = streamRes.iconRes; 651 sc.iconMuteRes = streamRes.iconMuteRes; 652 sc.icon.setImageResource(sc.iconRes); 653 sc.icon.setClickable(isNotification && mHasVibrator); 654 if (isNotification) { 655 if (mHasVibrator) { 656 sc.icon.setSoundEffectsEnabled(false); 657 sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate; 658 sc.icon.setOnClickListener(new OnClickListener() { 659 @Override 660 public void onClick(View v) { 661 resetTimeout(); 662 toggleRinger(sc); 663 } 664 }); 665 } 666 sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute; 667 } 668 sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); 669 sc.suppressorView = 670 (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor); 671 sc.suppressorView.setVisibility(View.GONE); 672 final boolean showSecondary = !isNotification && notificationStream.show; 673 sc.divider = sc.group.findViewById(com.android.systemui.R.id.divider); 674 sc.secondaryIcon = (ImageView) sc.group 675 .findViewById(com.android.systemui.R.id.secondary_icon); 676 sc.secondaryIcon.setImageResource(com.android.systemui.R.drawable.ic_ringer_audible); 677 sc.secondaryIcon.setContentDescription(res.getString(notificationStream.descRes)); 678 sc.secondaryIcon.setClickable(showSecondary); 679 sc.divider.setVisibility(showSecondary ? View.VISIBLE : View.GONE); 680 sc.secondaryIcon.setVisibility(showSecondary ? View.VISIBLE : View.GONE); 681 if (showSecondary) { 682 sc.secondaryIcon.setOnClickListener(new OnClickListener() { 683 @Override 684 public void onClick(View v) { 685 mSecondaryIconTransition.start(sc); 686 } 687 }); 688 } 689 final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || 690 streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; 691 sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); 692 sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); 693 sc.seekbarView.setTag(sc); 694 mStreamControls.put(streamType, sc); 695 } 696 } 697 698 private void toggleRinger(StreamControl sc) { 699 if (!mHasVibrator) return; 700 if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) { 701 mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); 702 postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); 703 } else { 704 mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); 705 postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND); 706 } 707 } 708 709 private void reorderSliders(int activeStreamType) { 710 mSliderPanel.removeAllViews(); 711 712 final StreamControl active = mStreamControls.get(activeStreamType); 713 if (active == null) { 714 Log.e(TAG, "Missing stream type! - " + activeStreamType); 715 mActiveStreamType = -1; 716 } else { 717 mSliderPanel.addView(active.group); 718 mActiveStreamType = activeStreamType; 719 active.group.setVisibility(View.VISIBLE); 720 updateSlider(active); 721 updateTimeoutDelay(); 722 updateZenPanelVisible(); 723 } 724 } 725 726 private void updateSliderProgress(StreamControl sc, int progress) { 727 final boolean isRinger = isNotificationOrRing(sc.streamType); 728 if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { 729 progress = mLastRingerProgress; 730 } 731 if (progress < 0) { 732 progress = getStreamVolume(sc.streamType); 733 } 734 sc.seekbarView.setProgress(progress); 735 if (isRinger) { 736 mLastRingerProgress = progress; 737 } 738 } 739 740 private void updateSliderIcon(StreamControl sc, boolean muted) { 741 ComponentName suppressor = null; 742 if (isNotificationOrRing(sc.streamType)) { 743 suppressor = mNotificationEffectsSuppressor; 744 int ringerMode = mAudioManager.getRingerModeInternal(); 745 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 746 ringerMode = mLastRingerMode; 747 } else { 748 mLastRingerMode = ringerMode; 749 } 750 if (mHasVibrator) { 751 muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE; 752 } else { 753 muted = false; 754 } 755 } 756 sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon 757 : suppressor != null ? sc.iconSuppressedRes 758 : muted ? sc.iconMuteRes 759 : sc.iconRes); 760 } 761 762 private void updateSliderSuppressor(StreamControl sc) { 763 final ComponentName suppressor = isNotificationOrRing(sc.streamType) 764 ? mNotificationEffectsSuppressor : null; 765 if (suppressor == null) { 766 sc.seekbarView.setVisibility(View.VISIBLE); 767 sc.suppressorView.setVisibility(View.GONE); 768 } else { 769 sc.seekbarView.setVisibility(View.GONE); 770 sc.suppressorView.setVisibility(View.VISIBLE); 771 sc.suppressorView.setText(mContext.getString(R.string.muted_by, 772 getSuppressorCaption(suppressor))); 773 } 774 } 775 776 private String getSuppressorCaption(ComponentName suppressor) { 777 final PackageManager pm = mContext.getPackageManager(); 778 try { 779 final ServiceInfo info = pm.getServiceInfo(suppressor, 0); 780 if (info != null) { 781 final CharSequence seq = info.loadLabel(pm); 782 if (seq != null) { 783 final String str = seq.toString().trim(); 784 if (str.length() > 0) { 785 return str; 786 } 787 } 788 } 789 } catch (Throwable e) { 790 Log.w(TAG, "Error loading suppressor caption", e); 791 } 792 return suppressor.getPackageName(); 793 } 794 795 /** Update the mute and progress state of a slider */ 796 private void updateSlider(StreamControl sc) { 797 updateSliderProgress(sc, -1); 798 final boolean muted = isMuted(sc.streamType); 799 // Force reloading the image resource 800 sc.icon.setImageDrawable(null); 801 updateSliderIcon(sc, muted); 802 updateSliderEnabled(sc, muted, false); 803 updateSliderSuppressor(sc); 804 } 805 806 private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) { 807 final boolean wasEnabled = sc.seekbarView.isEnabled(); 808 final boolean isRinger = isNotificationOrRing(sc.streamType); 809 if (sc.streamType == STREAM_REMOTE_MUSIC) { 810 // never disable touch interactions for remote playback, the muting is not tied to 811 // the state of the phone. 812 sc.seekbarView.setEnabled(!fixedVolume); 813 } else if (isRinger && mNotificationEffectsSuppressor != null) { 814 sc.icon.setEnabled(true); 815 sc.icon.setAlpha(1f); 816 sc.icon.setClickable(false); 817 } else if (isRinger 818 && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { 819 sc.seekbarView.setEnabled(false); 820 sc.icon.setEnabled(false); 821 sc.icon.setAlpha(mDisabledAlpha); 822 sc.icon.setClickable(false); 823 } else if (fixedVolume || 824 (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) || 825 (sSafetyWarning != null)) { 826 sc.seekbarView.setEnabled(false); 827 } else { 828 sc.seekbarView.setEnabled(true); 829 sc.icon.setEnabled(true); 830 sc.icon.setAlpha(1f); 831 } 832 // show the silent hint when the disabled slider is touched in silent mode 833 if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) { 834 if (sc.seekbarView.isEnabled()) { 835 sc.group.setOnTouchListener(null); 836 sc.icon.setClickable(mHasVibrator); 837 } else { 838 final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() { 839 @Override 840 public boolean onTouch(View v, MotionEvent event) { 841 resetTimeout(); 842 showSilentHint(); 843 return false; 844 } 845 }; 846 sc.group.setOnTouchListener(showHintOnTouch); 847 } 848 } 849 } 850 851 private void showSilentHint() { 852 if (mZenPanel != null) { 853 mZenPanel.showSilentHint(); 854 } 855 } 856 857 private void showVibrateHint() { 858 final StreamControl active = mStreamControls.get(mActiveStreamType); 859 if (active != null) { 860 mIconPulser.start(active.icon); 861 if (!hasMessages(MSG_VIBRATE)) { 862 sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY); 863 } 864 } 865 } 866 867 private static boolean isNotificationOrRing(int streamType) { 868 return streamType == AudioManager.STREAM_RING 869 || streamType == AudioManager.STREAM_NOTIFICATION; 870 } 871 872 public void setCallback(Callback callback) { 873 mCallback = callback; 874 } 875 876 private void updateTimeoutDelay() { 877 mTimeoutDelay = mDemoIcon != 0 ? TIMEOUT_DELAY_EXPANDED 878 : sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING 879 : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT 880 : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED 881 : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED 882 : TIMEOUT_DELAY; 883 } 884 885 private boolean isZenPanelVisible() { 886 return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE; 887 } 888 889 private void setZenPanelVisible(boolean visible) { 890 if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel); 891 final boolean changing = visible != isZenPanelVisible(); 892 if (visible) { 893 mZenPanel.setHidden(false); 894 resetTimeout(); 895 } else { 896 mZenPanel.setHidden(true); 897 } 898 if (changing) { 899 updateTimeoutDelay(); 900 resetTimeout(); 901 } 902 } 903 904 public void updateStates() { 905 final int count = mSliderPanel.getChildCount(); 906 for (int i = 0; i < count; i++) { 907 StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag(); 908 updateSlider(sc); 909 } 910 } 911 912 private void updateZenPanelVisible() { 913 setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType)); 914 } 915 916 public void postVolumeChanged(int streamType, int flags) { 917 if (hasMessages(MSG_VOLUME_CHANGED)) return; 918 synchronized (this) { 919 if (mStreamControls == null) { 920 createSliders(); 921 } 922 } 923 removeMessages(MSG_FREE_RESOURCES); 924 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); 925 } 926 927 public void postRemoteVolumeChanged(MediaController controller, int flags) { 928 if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; 929 synchronized (this) { 930 if (mStreamControls == null) { 931 createSliders(); 932 } 933 } 934 removeMessages(MSG_FREE_RESOURCES); 935 obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget(); 936 } 937 938 public void postRemoteSliderVisibility(boolean visible) { 939 obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, 940 STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); 941 } 942 943 /** 944 * Called by AudioService when it has received new remote playback information that 945 * would affect the VolumePanel display (mainly volumes). The difference with 946 * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message 947 * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being 948 * displayed. 949 * This special code path is due to the fact that remote volume updates arrive to AudioService 950 * asynchronously. So after AudioService has sent the volume update (which should be treated 951 * as a request to update the volume), the application will likely set a new volume. If the UI 952 * is still up, we need to refresh the display to show this new value. 953 */ 954 public void postHasNewRemotePlaybackInfo() { 955 if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; 956 // don't create or prevent resources to be freed, if they disappear, this update came too 957 // late and shouldn't warrant the panel to be displayed longer 958 obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); 959 } 960 961 public void postMasterVolumeChanged(int flags) { 962 postVolumeChanged(STREAM_MASTER, flags); 963 } 964 965 public void postMuteChanged(int streamType, int flags) { 966 if (hasMessages(MSG_VOLUME_CHANGED)) return; 967 synchronized (this) { 968 if (mStreamControls == null) { 969 createSliders(); 970 } 971 } 972 removeMessages(MSG_FREE_RESOURCES); 973 obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); 974 } 975 976 public void postMasterMuteChanged(int flags) { 977 postMuteChanged(STREAM_MASTER, flags); 978 } 979 980 public void postDisplaySafeVolumeWarning(int flags) { 981 if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; 982 obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget(); 983 } 984 985 public void postDismiss(long delay) { 986 forceTimeout(delay); 987 } 988 989 public void postLayoutDirection(int layoutDirection) { 990 removeMessages(MSG_LAYOUT_DIRECTION); 991 obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget(); 992 } 993 994 public void postInternalRingerModeChanged(int mode) { 995 removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED); 996 obtainMessage(MSG_INTERNAL_RINGER_MODE_CHANGED, mode, 0).sendToTarget(); 997 } 998 999 private static String flagsToString(int flags) { 1000 return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags)); 1001 } 1002 1003 private static String streamToString(int stream) { 1004 return AudioService.streamToString(stream); 1005 } 1006 1007 /** 1008 * Override this if you have other work to do when the volume changes (for 1009 * example, vibrating, playing a sound, etc.). Make sure to call through to 1010 * the superclass implementation. 1011 */ 1012 protected void onVolumeChanged(int streamType, int flags) { 1013 1014 if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType) 1015 + ", flags: " + flagsToString(flags) + ")"); 1016 1017 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 1018 synchronized (this) { 1019 if (mActiveStreamType != streamType) { 1020 reorderSliders(streamType); 1021 } 1022 onShowVolumeChanged(streamType, flags, null); 1023 } 1024 } 1025 1026 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 1027 removeMessages(MSG_PLAY_SOUND); 1028 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 1029 } 1030 1031 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 1032 removeMessages(MSG_PLAY_SOUND); 1033 removeMessages(MSG_VIBRATE); 1034 onStopSounds(); 1035 } 1036 1037 removeMessages(MSG_FREE_RESOURCES); 1038 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 1039 resetTimeout(); 1040 } 1041 1042 protected void onMuteChanged(int streamType, int flags) { 1043 1044 if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType) 1045 + ", flags: " + flagsToString(flags) + ")"); 1046 1047 StreamControl sc = mStreamControls.get(streamType); 1048 if (sc != null) { 1049 updateSliderIcon(sc, isMuted(sc.streamType)); 1050 } 1051 1052 onVolumeChanged(streamType, flags); 1053 } 1054 1055 protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) { 1056 int index = getStreamVolume(streamType); 1057 1058 mRingIsSilent = false; 1059 1060 if (LOGD) { 1061 Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType) 1062 + ", flags: " + flagsToString(flags) + "), index: " + index); 1063 } 1064 1065 // get max volume for progress bar 1066 1067 int max = getStreamMaxVolume(streamType); 1068 StreamControl sc = mStreamControls.get(streamType); 1069 1070 switch (streamType) { 1071 1072 case AudioManager.STREAM_RING: { 1073 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 1074 mContext, RingtoneManager.TYPE_RINGTONE); 1075 if (ringuri == null) { 1076 mRingIsSilent = true; 1077 } 1078 break; 1079 } 1080 1081 case AudioManager.STREAM_MUSIC: { 1082 // Special case for when Bluetooth is active for music 1083 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) & 1084 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | 1085 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | 1086 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { 1087 setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE); 1088 } else { 1089 setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE); 1090 } 1091 break; 1092 } 1093 1094 case AudioManager.STREAM_VOICE_CALL: { 1095 /* 1096 * For in-call voice call volume, there is no inaudible volume. 1097 * Rescale the UI control so the progress bar doesn't go all 1098 * the way to zero and don't show the mute icon. 1099 */ 1100 index++; 1101 max++; 1102 break; 1103 } 1104 1105 case AudioManager.STREAM_ALARM: { 1106 break; 1107 } 1108 1109 case AudioManager.STREAM_NOTIFICATION: { 1110 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 1111 mContext, RingtoneManager.TYPE_NOTIFICATION); 1112 if (ringuri == null) { 1113 mRingIsSilent = true; 1114 } 1115 break; 1116 } 1117 1118 case AudioManager.STREAM_BLUETOOTH_SCO: { 1119 /* 1120 * For in-call voice call volume, there is no inaudible volume. 1121 * Rescale the UI control so the progress bar doesn't go all 1122 * the way to zero and don't show the mute icon. 1123 */ 1124 index++; 1125 max++; 1126 break; 1127 } 1128 1129 case STREAM_REMOTE_MUSIC: { 1130 if (controller == null && sc != null) { 1131 // If we weren't passed one try using the last one set. 1132 controller = sc.controller; 1133 } 1134 if (controller == null) { 1135 // We still don't have one, ignore the command. 1136 Log.w(mTag, "sent remote volume change without a controller!"); 1137 } else { 1138 PlaybackInfo vi = controller.getPlaybackInfo(); 1139 index = vi.getCurrentVolume(); 1140 max = vi.getMaxVolume(); 1141 if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) { 1142 // if the remote volume is fixed add the flag for the UI 1143 flags |= AudioManager.FLAG_FIXED_VOLUME; 1144 } 1145 } 1146 if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); } 1147 break; 1148 } 1149 } 1150 1151 if (sc != null) { 1152 if (streamType == STREAM_REMOTE_MUSIC && controller != sc.controller) { 1153 if (sc.controller != null) { 1154 sc.controller.unregisterCallback(mMediaControllerCb); 1155 } 1156 sc.controller = controller; 1157 if (controller != null) { 1158 sc.controller.registerCallback(mMediaControllerCb); 1159 } 1160 } 1161 if (sc.seekbarView.getMax() != max) { 1162 sc.seekbarView.setMax(max); 1163 } 1164 updateSliderProgress(sc, index); 1165 final boolean muted = isMuted(streamType); 1166 updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0); 1167 if (isNotificationOrRing(streamType)) { 1168 // check for secondary-icon transition completion 1169 if (mSecondaryIconTransition.isRunning()) { 1170 mSecondaryIconTransition.cancel(); // safe to reset 1171 sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1); 1172 mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1); 1173 } 1174 updateSliderIcon(sc, muted); 1175 } 1176 } 1177 1178 if (!isShowing()) { 1179 int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType; 1180 // when the stream is for remote playback, use -1 to reset the stream type evaluation 1181 mAudioManager.forceVolumeControlStream(stream); 1182 mDialog.show(); 1183 if (mCallback != null) { 1184 mCallback.onVisible(true); 1185 } 1186 announceDialogShown(); 1187 } 1188 1189 // Do a little vibrate if applicable (only when going into vibrate mode) 1190 if ((streamType != STREAM_REMOTE_MUSIC) && 1191 ((flags & AudioManager.FLAG_VIBRATE) != 0) && 1192 isNotificationOrRing(streamType) && 1193 mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { 1194 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); 1195 } 1196 1197 // Pulse the zen icon if an adjustment was suppressed due to silent mode. 1198 if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { 1199 showSilentHint(); 1200 } 1201 1202 // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode. 1203 if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { 1204 showVibrateHint(); 1205 } 1206 } 1207 1208 private void announceDialogShown() { 1209 mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 1210 } 1211 1212 private boolean isShowing() { 1213 return mDialog.isShowing(); 1214 } 1215 1216 protected void onPlaySound(int streamType, int flags) { 1217 1218 if (hasMessages(MSG_STOP_SOUNDS)) { 1219 removeMessages(MSG_STOP_SOUNDS); 1220 // Force stop right now 1221 onStopSounds(); 1222 } 1223 1224 synchronized (this) { 1225 ToneGenerator toneGen = getOrCreateToneGenerator(streamType); 1226 if (toneGen != null) { 1227 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); 1228 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); 1229 } 1230 } 1231 } 1232 1233 protected void onStopSounds() { 1234 1235 synchronized (this) { 1236 int numStreamTypes = AudioSystem.getNumStreamTypes(); 1237 for (int i = numStreamTypes - 1; i >= 0; i--) { 1238 ToneGenerator toneGen = mToneGenerators[i]; 1239 if (toneGen != null) { 1240 toneGen.stopTone(); 1241 } 1242 } 1243 } 1244 } 1245 1246 protected void onVibrate() { 1247 1248 // Make sure we ended up in vibrate ringer mode 1249 if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) { 1250 return; 1251 } 1252 if (mVibrator != null) { 1253 mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES); 1254 } 1255 } 1256 1257 protected void onRemoteVolumeChanged(MediaController controller, int flags) { 1258 if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " 1259 + flagsToString(flags) + ")"); 1260 1261 if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) { 1262 synchronized (this) { 1263 if (mActiveStreamType != STREAM_REMOTE_MUSIC) { 1264 reorderSliders(STREAM_REMOTE_MUSIC); 1265 } 1266 onShowVolumeChanged(STREAM_REMOTE_MUSIC, flags, controller); 1267 } 1268 } else { 1269 if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); 1270 } 1271 1272 removeMessages(MSG_FREE_RESOURCES); 1273 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 1274 resetTimeout(); 1275 } 1276 1277 protected void onRemoteVolumeUpdateIfShown() { 1278 if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()"); 1279 if (isShowing() 1280 && (mActiveStreamType == STREAM_REMOTE_MUSIC) 1281 && (mStreamControls != null)) { 1282 onShowVolumeChanged(STREAM_REMOTE_MUSIC, 0, null); 1283 } 1284 } 1285 1286 /** 1287 * Clear the current remote stream controller. 1288 */ 1289 private void clearRemoteStreamController() { 1290 if (mStreamControls != null) { 1291 StreamControl sc = mStreamControls.get(STREAM_REMOTE_MUSIC); 1292 if (sc != null) { 1293 if (sc.controller != null) { 1294 sc.controller.unregisterCallback(mMediaControllerCb); 1295 sc.controller = null; 1296 } 1297 } 1298 } 1299 } 1300 1301 /** 1302 * Handler for MSG_SLIDER_VISIBILITY_CHANGED Hide or show a slider 1303 * 1304 * @param streamType can be a valid stream type value, or 1305 * VolumePanel.STREAM_MASTER, or VolumePanel.STREAM_REMOTE_MUSIC 1306 * @param visible 1307 */ 1308 synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { 1309 if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); 1310 boolean isVisible = (visible == 1); 1311 for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { 1312 StreamResources streamRes = STREAMS[i]; 1313 if (streamRes.streamType == streamType) { 1314 streamRes.show = isVisible; 1315 if (!isVisible && (mActiveStreamType == streamType)) { 1316 mActiveStreamType = -1; 1317 } 1318 break; 1319 } 1320 } 1321 } 1322 1323 protected void onDisplaySafeVolumeWarning(int flags) { 1324 if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 1325 || isShowing()) { 1326 synchronized (sSafetyWarningLock) { 1327 if (sSafetyWarning != null) { 1328 return; 1329 } 1330 sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager); 1331 sSafetyWarning.show(); 1332 } 1333 updateStates(); 1334 } 1335 if (mAccessibilityManager.isTouchExplorationEnabled()) { 1336 removeMessages(MSG_TIMEOUT); 1337 } else { 1338 updateTimeoutDelay(); 1339 resetTimeout(); 1340 } 1341 } 1342 1343 /** 1344 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. 1345 */ 1346 private ToneGenerator getOrCreateToneGenerator(int streamType) { 1347 if (streamType == STREAM_MASTER) { 1348 // For devices that use the master volume setting only but still want to 1349 // play a volume-changed tone, direct the master volume pseudostream to 1350 // the system stream's tone generator. 1351 if (mPlayMasterStreamTones) { 1352 streamType = AudioManager.STREAM_SYSTEM; 1353 } else { 1354 return null; 1355 } 1356 } 1357 synchronized (this) { 1358 if (mToneGenerators[streamType] == null) { 1359 try { 1360 mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); 1361 } catch (RuntimeException e) { 1362 if (LOGD) { 1363 Log.d(mTag, "ToneGenerator constructor failed with " 1364 + "RuntimeException: " + e); 1365 } 1366 } 1367 } 1368 return mToneGenerators[streamType]; 1369 } 1370 } 1371 1372 1373 /** 1374 * Switch between icons because Bluetooth music is same as music volume, but with 1375 * different icons. 1376 */ 1377 private void setMusicIcon(int resId, int resMuteId) { 1378 StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC); 1379 if (sc != null) { 1380 sc.iconRes = resId; 1381 sc.iconMuteRes = resMuteId; 1382 updateSliderIcon(sc, isMuted(sc.streamType)); 1383 } 1384 } 1385 1386 protected void onFreeResources() { 1387 synchronized (this) { 1388 for (int i = mToneGenerators.length - 1; i >= 0; i--) { 1389 if (mToneGenerators[i] != null) { 1390 mToneGenerators[i].release(); 1391 } 1392 mToneGenerators[i] = null; 1393 } 1394 } 1395 } 1396 1397 @Override 1398 public void handleMessage(Message msg) { 1399 switch (msg.what) { 1400 1401 case MSG_VOLUME_CHANGED: { 1402 onVolumeChanged(msg.arg1, msg.arg2); 1403 break; 1404 } 1405 1406 case MSG_MUTE_CHANGED: { 1407 onMuteChanged(msg.arg1, msg.arg2); 1408 break; 1409 } 1410 1411 case MSG_FREE_RESOURCES: { 1412 onFreeResources(); 1413 break; 1414 } 1415 1416 case MSG_STOP_SOUNDS: { 1417 onStopSounds(); 1418 break; 1419 } 1420 1421 case MSG_PLAY_SOUND: { 1422 onPlaySound(msg.arg1, msg.arg2); 1423 break; 1424 } 1425 1426 case MSG_VIBRATE: { 1427 onVibrate(); 1428 break; 1429 } 1430 1431 case MSG_TIMEOUT: { 1432 if (isShowing()) { 1433 mDialog.dismiss(); 1434 clearRemoteStreamController(); 1435 mActiveStreamType = -1; 1436 if (mCallback != null) { 1437 mCallback.onVisible(false); 1438 } 1439 } 1440 synchronized (sSafetyWarningLock) { 1441 if (sSafetyWarning != null) { 1442 if (LOGD) Log.d(mTag, "SafetyWarning timeout"); 1443 sSafetyWarning.dismiss(); 1444 } 1445 } 1446 break; 1447 } 1448 1449 case MSG_ZEN_MODE_CHANGED: 1450 case MSG_RINGER_MODE_CHANGED: 1451 case MSG_INTERNAL_RINGER_MODE_CHANGED: 1452 case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: { 1453 if (isShowing()) { 1454 updateStates(); 1455 } 1456 break; 1457 } 1458 1459 case MSG_REMOTE_VOLUME_CHANGED: { 1460 onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1); 1461 break; 1462 } 1463 1464 case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: 1465 onRemoteVolumeUpdateIfShown(); 1466 break; 1467 1468 case MSG_SLIDER_VISIBILITY_CHANGED: 1469 onSliderVisibilityChanged(msg.arg1, msg.arg2); 1470 break; 1471 1472 case MSG_DISPLAY_SAFE_VOLUME_WARNING: 1473 onDisplaySafeVolumeWarning(msg.arg1); 1474 break; 1475 1476 case MSG_LAYOUT_DIRECTION: 1477 setLayoutDirection(msg.arg1); 1478 break; 1479 1480 case MSG_ZEN_MODE_AVAILABLE_CHANGED: 1481 mZenModeAvailable = msg.arg1 != 0; 1482 updateZenPanelVisible(); 1483 break; 1484 1485 case MSG_USER_ACTIVITY: 1486 if (mCallback != null) { 1487 mCallback.onInteraction(); 1488 } 1489 break; 1490 } 1491 } 1492 1493 private void resetTimeout() { 1494 final boolean touchExploration = mAccessibilityManager.isTouchExplorationEnabled(); 1495 if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis() 1496 + " delay=" + mTimeoutDelay + " touchExploration=" + touchExploration); 1497 if (sSafetyWarning == null || !touchExploration) { 1498 removeMessages(MSG_TIMEOUT); 1499 sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay); 1500 removeMessages(MSG_USER_ACTIVITY); 1501 sendEmptyMessage(MSG_USER_ACTIVITY); 1502 } 1503 } 1504 1505 private void forceTimeout(long delay) { 1506 if (LOGD) Log.d(mTag, "forceTimeout delay=" + delay + " callers=" + Debug.getCallers(3)); 1507 removeMessages(MSG_TIMEOUT); 1508 sendEmptyMessageDelayed(MSG_TIMEOUT, delay); 1509 } 1510 1511 public ZenModeController getZenController() { 1512 return mZenController; 1513 } 1514 1515 @Override 1516 public void dispatchDemoCommand(String command, Bundle args) { 1517 if (!COMMAND_VOLUME.equals(command)) return; 1518 String icon = args.getString("icon"); 1519 final String iconMute = args.getString("iconmute"); 1520 final boolean mute = iconMute != null; 1521 icon = mute ? iconMute : icon; 1522 icon = icon.endsWith("Stream") ? icon : (icon + "Stream"); 1523 final StreamResources sr = StreamResources.valueOf(icon); 1524 mDemoIcon = mute ? sr.iconMuteRes : sr.iconRes; 1525 final int forcedStreamType = StreamResources.MediaStream.streamType; 1526 mAudioManager.forceVolumeControlStream(forcedStreamType); 1527 mAudioManager.adjustStreamVolume(forcedStreamType, AudioManager.ADJUST_SAME, 1528 AudioManager.FLAG_SHOW_UI); 1529 } 1530 1531 private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { 1532 @Override 1533 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 1534 final Object tag = seekBar.getTag(); 1535 if (fromUser && tag instanceof StreamControl) { 1536 StreamControl sc = (StreamControl) tag; 1537 setStreamVolume(sc, progress, 1538 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); 1539 } 1540 resetTimeout(); 1541 } 1542 1543 @Override 1544 public void onStartTrackingTouch(SeekBar seekBar) { 1545 } 1546 1547 @Override 1548 public void onStopTrackingTouch(SeekBar seekBar) { 1549 } 1550 }; 1551 1552 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { 1553 @Override 1554 public void onZenAvailableChanged(boolean available) { 1555 obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget(); 1556 } 1557 1558 @Override 1559 public void onEffectsSupressorChanged() { 1560 mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); 1561 sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED); 1562 } 1563 1564 public void onZenChanged(int zen) { 1565 sendEmptyMessage(MSG_ZEN_MODE_CHANGED); 1566 } 1567 }; 1568 1569 private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() { 1570 public void onAudioInfoChanged(PlaybackInfo info) { 1571 onRemoteVolumeUpdateIfShown(); 1572 } 1573 }; 1574 1575 private final class SecondaryIconTransition extends AnimatorListenerAdapter 1576 implements Runnable { 1577 private static final int ANIMATION_TIME = 400; 1578 private static final int WAIT_FOR_SWITCH_TIME = 1000; 1579 1580 private final int mAnimationTime = (int)(ANIMATION_TIME * ValueAnimator.getDurationScale()); 1581 private final int mFadeOutTime = mAnimationTime / 2; 1582 private final int mDelayTime = mAnimationTime / 3; 1583 1584 private final Interpolator mIconInterpolator = 1585 AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); 1586 1587 private StreamControl mTarget; 1588 1589 public void start(StreamControl sc) { 1590 if (sc == null) throw new IllegalArgumentException(); 1591 if (mTarget != null) { 1592 cancel(); 1593 } 1594 mTarget = sc; 1595 mTimeoutDelay = mAnimationTime + WAIT_FOR_SWITCH_TIME; 1596 resetTimeout(); 1597 mTarget.secondaryIcon.setClickable(false); 1598 final int N = mTarget.group.getChildCount(); 1599 for (int i = 0; i < N; i++) { 1600 final View child = mTarget.group.getChildAt(i); 1601 if (child != mTarget.secondaryIcon) { 1602 child.animate().alpha(0).setDuration(mFadeOutTime).start(); 1603 } 1604 } 1605 mTarget.secondaryIcon.animate() 1606 .translationXBy(mTarget.icon.getX() - mTarget.secondaryIcon.getX()) 1607 .setInterpolator(mIconInterpolator) 1608 .setStartDelay(mDelayTime) 1609 .setDuration(mAnimationTime - mDelayTime) 1610 .setListener(this) 1611 .start(); 1612 } 1613 1614 public boolean isRunning() { 1615 return mTarget != null; 1616 } 1617 1618 public void cancel() { 1619 if (mTarget == null) return; 1620 mTarget.secondaryIcon.setClickable(true); 1621 final int N = mTarget.group.getChildCount(); 1622 for (int i = 0; i < N; i++) { 1623 final View child = mTarget.group.getChildAt(i); 1624 if (child != mTarget.secondaryIcon) { 1625 child.animate().cancel(); 1626 child.setAlpha(1); 1627 } 1628 } 1629 mTarget.secondaryIcon.animate().cancel(); 1630 mTarget.secondaryIcon.setTranslationX(0); 1631 mTarget = null; 1632 } 1633 1634 @Override 1635 public void onAnimationEnd(Animator animation) { 1636 if (mTarget == null) return; 1637 AsyncTask.execute(this); 1638 } 1639 1640 @Override 1641 public void run() { 1642 if (mTarget == null) return; 1643 mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType); 1644 mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType, 1645 AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI); 1646 } 1647 } 1648 1649 public interface Callback { 1650 void onZenSettings(); 1651 void onInteraction(); 1652 void onVisible(boolean visible); 1653 } 1654} 1655