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