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