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