1 /* 2 * Copyright (C) 2015 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.app.NotificationManager; 20 import android.content.BroadcastReceiver; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.database.ContentObserver; 29 import android.media.AudioManager; 30 import android.media.AudioSystem; 31 import android.media.IVolumeController; 32 import android.media.VolumePolicy; 33 import android.media.session.MediaController.PlaybackInfo; 34 import android.media.session.MediaSession.Token; 35 import android.net.Uri; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.Looper; 39 import android.os.Message; 40 import android.os.RemoteException; 41 import android.os.Vibrator; 42 import android.provider.Settings; 43 import android.service.notification.Condition; 44 import android.util.ArrayMap; 45 import android.util.Log; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.systemui.Dumpable; 49 import com.android.systemui.R; 50 import com.android.systemui.plugins.VolumeDialogController; 51 import com.android.systemui.qs.tiles.DndTile; 52 53 import java.io.FileDescriptor; 54 import java.io.PrintWriter; 55 import java.util.HashMap; 56 import java.util.Map; 57 import java.util.Objects; 58 59 /** 60 * Source of truth for all state / events related to the volume dialog. No presentation. 61 * 62 * All work done on a dedicated background worker thread & associated worker. 63 * 64 * Methods ending in "W" must be called on the worker thread. 65 */ 66 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable { 67 private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class); 68 69 private static final int DYNAMIC_STREAM_START_INDEX = 100; 70 private static final int VIBRATE_HINT_DURATION = 50; 71 72 private static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); 73 static { STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm)74 STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco)75 STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf)76 STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf); STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music)77 STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music); STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility)78 STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility); STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification)79 STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification); STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring)80 STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring); STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system)81 STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system); STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced)82 STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced); STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts)83 STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts); STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call)84 STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call); 85 } 86 87 private final HandlerThread mWorkerThread; 88 private final W mWorker; 89 private final Context mContext; 90 private AudioManager mAudio; 91 private final NotificationManager mNoMan; 92 private final SettingObserver mObserver; 93 private final Receiver mReceiver = new Receiver(); 94 private final MediaSessions mMediaSessions; 95 private final C mCallbacks = new C(); 96 private final State mState = new State(); 97 private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); 98 private final Vibrator mVibrator; 99 private final boolean mHasVibrator; 100 private boolean mShowA11yStream; 101 102 private boolean mDestroyed; 103 private VolumePolicy mVolumePolicy; 104 private boolean mShowDndTile = true; 105 @GuardedBy("this") 106 private UserActivityListener mUserActivityListener; 107 108 protected final VC mVolumeController = new VC(); 109 VolumeDialogControllerImpl(Context context)110 public VolumeDialogControllerImpl(Context context) { 111 mContext = context.getApplicationContext(); 112 Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED); 113 mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName()); 114 mWorkerThread.start(); 115 mWorker = new W(mWorkerThread.getLooper()); 116 mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(), 117 mMediaSessionsCallbacksW); 118 mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 119 mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 120 mObserver = new SettingObserver(mWorker); 121 mObserver.init(); 122 mReceiver.init(); 123 mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 124 mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); 125 } 126 getAudioManager()127 public AudioManager getAudioManager() { 128 return mAudio; 129 } 130 dismiss()131 public void dismiss() { 132 mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER); 133 } 134 setVolumeController()135 protected void setVolumeController() { 136 try { 137 mAudio.setVolumeController(mVolumeController); 138 } catch (SecurityException e) { 139 Log.w(TAG, "Unable to set the volume controller", e); 140 return; 141 } 142 } 143 setAudioManagerStreamVolume(int stream, int level, int flag)144 protected void setAudioManagerStreamVolume(int stream, int level, int flag) { 145 mAudio.setStreamVolume(stream, level, flag); 146 } 147 getAudioManagerStreamVolume(int stream)148 protected int getAudioManagerStreamVolume(int stream) { 149 return mAudio.getLastAudibleStreamVolume(stream); 150 } 151 getAudioManagerStreamMaxVolume(int stream)152 protected int getAudioManagerStreamMaxVolume(int stream) { 153 return mAudio.getStreamMaxVolume(stream); 154 } 155 getAudioManagerStreamMinVolume(int stream)156 protected int getAudioManagerStreamMinVolume(int stream) { 157 return mAudio.getStreamMinVolume(stream); 158 } 159 register()160 public void register() { 161 setVolumeController(); 162 setVolumePolicy(mVolumePolicy); 163 showDndTile(mShowDndTile); 164 try { 165 mMediaSessions.init(); 166 } catch (SecurityException e) { 167 Log.w(TAG, "No access to media sessions", e); 168 } 169 } 170 setVolumePolicy(VolumePolicy policy)171 public void setVolumePolicy(VolumePolicy policy) { 172 mVolumePolicy = policy; 173 if (mVolumePolicy == null) return; 174 try { 175 mAudio.setVolumePolicy(mVolumePolicy); 176 } catch (NoSuchMethodError e) { 177 Log.w(TAG, "No volume policy api"); 178 } 179 } 180 createMediaSessions(Context context, Looper looper, MediaSessions.Callbacks callbacks)181 protected MediaSessions createMediaSessions(Context context, Looper looper, 182 MediaSessions.Callbacks callbacks) { 183 return new MediaSessions(context, looper, callbacks); 184 } 185 destroy()186 public void destroy() { 187 if (D.BUG) Log.d(TAG, "destroy"); 188 if (mDestroyed) return; 189 mDestroyed = true; 190 Events.writeEvent(mContext, Events.EVENT_COLLECTION_STOPPED); 191 mMediaSessions.destroy(); 192 mObserver.destroy(); 193 mReceiver.destroy(); 194 mWorkerThread.quitSafely(); 195 } 196 dump(FileDescriptor fd, PrintWriter pw, String[] args)197 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 198 pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:"); 199 pw.print(" mDestroyed: "); pw.println(mDestroyed); 200 pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy); 201 pw.print(" mState: "); pw.println(mState.toString(4)); 202 pw.print(" mShowDndTile: "); pw.println(mShowDndTile); 203 pw.print(" mHasVibrator: "); pw.println(mHasVibrator); 204 pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams 205 .values()); 206 pw.print(" mShowA11yStream: "); pw.println(mShowA11yStream); 207 pw.println(); 208 mMediaSessions.dump(pw); 209 } 210 addCallback(Callbacks callback, Handler handler)211 public void addCallback(Callbacks callback, Handler handler) { 212 mCallbacks.add(callback, handler); 213 } 214 setUserActivityListener(UserActivityListener listener)215 public void setUserActivityListener(UserActivityListener listener) { 216 if (mDestroyed) return; 217 synchronized (this) { 218 mUserActivityListener = listener; 219 } 220 } 221 removeCallback(Callbacks callback)222 public void removeCallback(Callbacks callback) { 223 mCallbacks.remove(callback); 224 } 225 getState()226 public void getState() { 227 if (mDestroyed) return; 228 mWorker.sendEmptyMessage(W.GET_STATE); 229 } 230 notifyVisible(boolean visible)231 public void notifyVisible(boolean visible) { 232 if (mDestroyed) return; 233 mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); 234 } 235 userActivity()236 public void userActivity() { 237 if (mDestroyed) return; 238 mWorker.removeMessages(W.USER_ACTIVITY); 239 mWorker.sendEmptyMessage(W.USER_ACTIVITY); 240 } 241 setRingerMode(int value, boolean external)242 public void setRingerMode(int value, boolean external) { 243 if (mDestroyed) return; 244 mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget(); 245 } 246 setZenMode(int value)247 public void setZenMode(int value) { 248 if (mDestroyed) return; 249 mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget(); 250 } 251 setExitCondition(Condition condition)252 public void setExitCondition(Condition condition) { 253 if (mDestroyed) return; 254 mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget(); 255 } 256 setStreamMute(int stream, boolean mute)257 public void setStreamMute(int stream, boolean mute) { 258 if (mDestroyed) return; 259 mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget(); 260 } 261 setStreamVolume(int stream, int level)262 public void setStreamVolume(int stream, int level) { 263 if (mDestroyed) return; 264 mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget(); 265 } 266 setActiveStream(int stream)267 public void setActiveStream(int stream) { 268 if (mDestroyed) return; 269 mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget(); 270 } 271 vibrate()272 public void vibrate() { 273 if (mHasVibrator) { 274 mVibrator.vibrate(VIBRATE_HINT_DURATION); 275 } 276 } 277 hasVibrator()278 public boolean hasVibrator() { 279 return mHasVibrator; 280 } 281 onNotifyVisibleW(boolean visible)282 private void onNotifyVisibleW(boolean visible) { 283 if (mDestroyed) return; 284 mAudio.notifyVolumeControllerVisible(mVolumeController, visible); 285 if (!visible) { 286 if (updateActiveStreamW(-1)) { 287 mCallbacks.onStateChanged(mState); 288 } 289 } 290 } 291 onUserActivityW()292 private void onUserActivityW() { 293 synchronized (this) { 294 if (mUserActivityListener != null) { 295 mUserActivityListener.onUserActivity(); 296 } 297 } 298 } 299 onShowSafetyWarningW(int flags)300 private void onShowSafetyWarningW(int flags) { 301 mCallbacks.onShowSafetyWarning(flags); 302 } 303 onAccessibilityModeChanged(Boolean showA11yStream)304 private void onAccessibilityModeChanged(Boolean showA11yStream) { 305 mCallbacks.onAccessibilityModeChanged(showA11yStream); 306 } 307 checkRoutedToBluetoothW(int stream)308 private boolean checkRoutedToBluetoothW(int stream) { 309 boolean changed = false; 310 if (stream == AudioManager.STREAM_MUSIC) { 311 final boolean routedToBluetooth = 312 (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) & 313 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | 314 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | 315 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0; 316 changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); 317 } 318 return changed; 319 } 320 onVolumeChangedW(int stream, int flags)321 private boolean onVolumeChangedW(int stream, int flags) { 322 final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0; 323 final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0; 324 final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0; 325 final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0; 326 boolean changed = false; 327 if (showUI) { 328 changed |= updateActiveStreamW(stream); 329 } 330 int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream); 331 changed |= updateStreamLevelW(stream, lastAudibleStreamVolume); 332 changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream); 333 if (changed) { 334 mCallbacks.onStateChanged(mState); 335 } 336 if (showUI) { 337 mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); 338 } 339 if (showVibrateHint) { 340 mCallbacks.onShowVibrateHint(); 341 } 342 if (showSilentHint) { 343 mCallbacks.onShowSilentHint(); 344 } 345 if (changed && fromKey) { 346 Events.writeEvent(mContext, Events.EVENT_KEY, stream, lastAudibleStreamVolume); 347 } 348 return changed; 349 } 350 updateActiveStreamW(int activeStream)351 private boolean updateActiveStreamW(int activeStream) { 352 if (activeStream == mState.activeStream) return false; 353 mState.activeStream = activeStream; 354 Events.writeEvent(mContext, Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream); 355 if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream); 356 final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1; 357 if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s); 358 mAudio.forceVolumeControlStream(s); 359 return true; 360 } 361 362 private StreamState streamStateW(int stream) { 363 StreamState ss = mState.states.get(stream); 364 if (ss == null) { 365 ss = new StreamState(); 366 mState.states.put(stream, ss); 367 } 368 return ss; 369 } 370 371 private void onGetStateW() { 372 for (int stream : STREAMS.keySet()) { 373 updateStreamLevelW(stream, getAudioManagerStreamVolume(stream)); 374 streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream); 375 streamStateW(stream).levelMax = getAudioManagerStreamMaxVolume(stream); 376 updateStreamMuteW(stream, mAudio.isStreamMute(stream)); 377 final StreamState ss = streamStateW(stream); 378 ss.muteSupported = mAudio.isStreamAffectedByMute(stream); 379 ss.name = STREAMS.get(stream); 380 checkRoutedToBluetoothW(stream); 381 } 382 updateRingerModeExternalW(mAudio.getRingerMode()); 383 updateZenModeW(); 384 updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); 385 mCallbacks.onStateChanged(mState); 386 } 387 388 private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) { 389 final StreamState ss = streamStateW(stream); 390 if (ss.routedToBluetooth == routedToBluetooth) return false; 391 ss.routedToBluetooth = routedToBluetooth; 392 if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream 393 + " routedToBluetooth=" + routedToBluetooth); 394 return true; 395 } 396 397 private boolean updateStreamLevelW(int stream, int level) { 398 final StreamState ss = streamStateW(stream); 399 if (ss.level == level) return false; 400 ss.level = level; 401 if (isLogWorthy(stream)) { 402 Events.writeEvent(mContext, Events.EVENT_LEVEL_CHANGED, stream, level); 403 } 404 return true; 405 } 406 407 private static boolean isLogWorthy(int stream) { 408 switch (stream) { 409 case AudioSystem.STREAM_ALARM: 410 case AudioSystem.STREAM_BLUETOOTH_SCO: 411 case AudioSystem.STREAM_MUSIC: 412 case AudioSystem.STREAM_RING: 413 case AudioSystem.STREAM_SYSTEM: 414 case AudioSystem.STREAM_VOICE_CALL: 415 return true; 416 } 417 return false; 418 } 419 420 private boolean updateStreamMuteW(int stream, boolean muted) { 421 final StreamState ss = streamStateW(stream); 422 if (ss.muted == muted) return false; 423 ss.muted = muted; 424 if (isLogWorthy(stream)) { 425 Events.writeEvent(mContext, Events.EVENT_MUTE_CHANGED, stream, muted); 426 } 427 if (muted && isRinger(stream)) { 428 updateRingerModeInternalW(mAudio.getRingerModeInternal()); 429 } 430 return true; 431 } 432 433 private static boolean isRinger(int stream) { 434 return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION; 435 } 436 437 private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) { 438 if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false; 439 mState.effectsSuppressor = effectsSuppressor; 440 mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor); 441 Events.writeEvent(mContext, Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor, 442 mState.effectsSuppressorName); 443 return true; 444 } 445 446 private static String getApplicationName(Context context, ComponentName component) { 447 if (component == null) return null; 448 final PackageManager pm = context.getPackageManager(); 449 final String pkg = component.getPackageName(); 450 try { 451 final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0); 452 final String rt = Objects.toString(ai.loadLabel(pm), "").trim(); 453 if (rt.length() > 0) { 454 return rt; 455 } 456 } catch (NameNotFoundException e) {} 457 return pkg; 458 } 459 updateZenModeW()460 private boolean updateZenModeW() { 461 final int zen = Settings.Global.getInt(mContext.getContentResolver(), 462 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 463 if (mState.zenMode == zen) return false; 464 mState.zenMode = zen; 465 Events.writeEvent(mContext, Events.EVENT_ZEN_MODE_CHANGED, zen); 466 return true; 467 } 468 updateRingerModeExternalW(int rm)469 private boolean updateRingerModeExternalW(int rm) { 470 if (rm == mState.ringerModeExternal) return false; 471 mState.ringerModeExternal = rm; 472 Events.writeEvent(mContext, Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm); 473 return true; 474 } 475 updateRingerModeInternalW(int rm)476 private boolean updateRingerModeInternalW(int rm) { 477 if (rm == mState.ringerModeInternal) return false; 478 mState.ringerModeInternal = rm; 479 Events.writeEvent(mContext, Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm); 480 return true; 481 } 482 onSetRingerModeW(int mode, boolean external)483 private void onSetRingerModeW(int mode, boolean external) { 484 if (external) { 485 mAudio.setRingerMode(mode); 486 } else { 487 mAudio.setRingerModeInternal(mode); 488 } 489 } 490 onSetStreamMuteW(int stream, boolean mute)491 private void onSetStreamMuteW(int stream, boolean mute) { 492 mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE 493 : AudioManager.ADJUST_UNMUTE, 0); 494 } 495 onSetStreamVolumeW(int stream, int level)496 private void onSetStreamVolumeW(int stream, int level) { 497 if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level); 498 if (stream >= DYNAMIC_STREAM_START_INDEX) { 499 mMediaSessionsCallbacksW.setStreamVolume(stream, level); 500 return; 501 } 502 setAudioManagerStreamVolume(stream, level, 0); 503 } 504 onSetActiveStreamW(int stream)505 private void onSetActiveStreamW(int stream) { 506 boolean changed = updateActiveStreamW(stream); 507 if (changed) { 508 mCallbacks.onStateChanged(mState); 509 } 510 } 511 onSetExitConditionW(Condition condition)512 private void onSetExitConditionW(Condition condition) { 513 mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG); 514 } 515 onSetZenModeW(int mode)516 private void onSetZenModeW(int mode) { 517 if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode); 518 mNoMan.setZenMode(mode, null, TAG); 519 } 520 onDismissRequestedW(int reason)521 private void onDismissRequestedW(int reason) { 522 mCallbacks.onDismissRequested(reason); 523 } 524 showDndTile(boolean visible)525 public void showDndTile(boolean visible) { 526 if (D.BUG) Log.d(TAG, "showDndTile"); 527 DndTile.setVisible(mContext, visible); 528 } 529 530 private final class VC extends IVolumeController.Stub { 531 private final String TAG = VolumeDialogControllerImpl.TAG + ".VC"; 532 533 @Override displaySafeVolumeWarning(int flags)534 public void displaySafeVolumeWarning(int flags) throws RemoteException { 535 if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning " 536 + Util.audioManagerFlagsToString(flags)); 537 if (mDestroyed) return; 538 mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget(); 539 } 540 541 @Override volumeChanged(int streamType, int flags)542 public void volumeChanged(int streamType, int flags) throws RemoteException { 543 if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) 544 + " " + Util.audioManagerFlagsToString(flags)); 545 if (mDestroyed) return; 546 mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); 547 } 548 549 @Override masterMuteChanged(int flags)550 public void masterMuteChanged(int flags) throws RemoteException { 551 if (D.BUG) Log.d(TAG, "masterMuteChanged"); 552 } 553 554 @Override setLayoutDirection(int layoutDirection)555 public void setLayoutDirection(int layoutDirection) throws RemoteException { 556 if (D.BUG) Log.d(TAG, "setLayoutDirection"); 557 if (mDestroyed) return; 558 mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget(); 559 } 560 561 @Override dismiss()562 public void dismiss() throws RemoteException { 563 if (D.BUG) Log.d(TAG, "dismiss requested"); 564 if (mDestroyed) return; 565 mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0) 566 .sendToTarget(); 567 mWorker.sendEmptyMessage(W.DISMISS_REQUESTED); 568 } 569 570 @Override setA11yMode(int mode)571 public void setA11yMode(int mode) { 572 if (D.BUG) Log.d(TAG, "setA11yMode to " + mode); 573 if (mDestroyed) return; 574 switch (mode) { 575 case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME: 576 // "legacy" mode 577 mShowA11yStream = false; 578 break; 579 case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME: 580 mShowA11yStream = true; 581 break; 582 default: 583 Log.e(TAG, "Invalid accessibility mode " + mode); 584 break; 585 } 586 mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget(); 587 } 588 } 589 590 private final class W extends Handler { 591 private static final int VOLUME_CHANGED = 1; 592 private static final int DISMISS_REQUESTED = 2; 593 private static final int GET_STATE = 3; 594 private static final int SET_RINGER_MODE = 4; 595 private static final int SET_ZEN_MODE = 5; 596 private static final int SET_EXIT_CONDITION = 6; 597 private static final int SET_STREAM_MUTE = 7; 598 private static final int LAYOUT_DIRECTION_CHANGED = 8; 599 private static final int CONFIGURATION_CHANGED = 9; 600 private static final int SET_STREAM_VOLUME = 10; 601 private static final int SET_ACTIVE_STREAM = 11; 602 private static final int NOTIFY_VISIBLE = 12; 603 private static final int USER_ACTIVITY = 13; 604 private static final int SHOW_SAFETY_WARNING = 14; 605 private static final int ACCESSIBILITY_MODE_CHANGED = 15; 606 W(Looper looper)607 W(Looper looper) { 608 super(looper); 609 } 610 611 @Override handleMessage(Message msg)612 public void handleMessage(Message msg) { 613 switch (msg.what) { 614 case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break; 615 case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break; 616 case GET_STATE: onGetStateW(); break; 617 case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break; 618 case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break; 619 case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break; 620 case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break; 621 case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break; 622 case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break; 623 case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break; 624 case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break; 625 case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; 626 case USER_ACTIVITY: onUserActivityW(); break; 627 case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; 628 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); 629 } 630 } 631 } 632 633 private final class C implements Callbacks { 634 private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>(); 635 add(Callbacks callback, Handler handler)636 public void add(Callbacks callback, Handler handler) { 637 if (callback == null || handler == null) throw new IllegalArgumentException(); 638 mCallbackMap.put(callback, handler); 639 } 640 remove(Callbacks callback)641 public void remove(Callbacks callback) { 642 mCallbackMap.remove(callback); 643 } 644 645 @Override onShowRequested(final int reason)646 public void onShowRequested(final int reason) { 647 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 648 entry.getValue().post(new Runnable() { 649 @Override 650 public void run() { 651 entry.getKey().onShowRequested(reason); 652 } 653 }); 654 } 655 } 656 657 @Override onDismissRequested(final int reason)658 public void onDismissRequested(final int reason) { 659 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 660 entry.getValue().post(new Runnable() { 661 @Override 662 public void run() { 663 entry.getKey().onDismissRequested(reason); 664 } 665 }); 666 } 667 } 668 669 @Override onStateChanged(final State state)670 public void onStateChanged(final State state) { 671 final long time = System.currentTimeMillis(); 672 final State copy = state.copy(); 673 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 674 entry.getValue().post(new Runnable() { 675 @Override 676 public void run() { 677 entry.getKey().onStateChanged(copy); 678 } 679 }); 680 } 681 Events.writeState(time, copy); 682 } 683 684 @Override onLayoutDirectionChanged(final int layoutDirection)685 public void onLayoutDirectionChanged(final int layoutDirection) { 686 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 687 entry.getValue().post(new Runnable() { 688 @Override 689 public void run() { 690 entry.getKey().onLayoutDirectionChanged(layoutDirection); 691 } 692 }); 693 } 694 } 695 696 @Override onConfigurationChanged()697 public void onConfigurationChanged() { 698 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 699 entry.getValue().post(new Runnable() { 700 @Override 701 public void run() { 702 entry.getKey().onConfigurationChanged(); 703 } 704 }); 705 } 706 } 707 708 @Override onShowVibrateHint()709 public void onShowVibrateHint() { 710 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 711 entry.getValue().post(new Runnable() { 712 @Override 713 public void run() { 714 entry.getKey().onShowVibrateHint(); 715 } 716 }); 717 } 718 } 719 720 @Override onShowSilentHint()721 public void onShowSilentHint() { 722 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 723 entry.getValue().post(new Runnable() { 724 @Override 725 public void run() { 726 entry.getKey().onShowSilentHint(); 727 } 728 }); 729 } 730 } 731 732 @Override onScreenOff()733 public void onScreenOff() { 734 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 735 entry.getValue().post(new Runnable() { 736 @Override 737 public void run() { 738 entry.getKey().onScreenOff(); 739 } 740 }); 741 } 742 } 743 744 @Override onShowSafetyWarning(final int flags)745 public void onShowSafetyWarning(final int flags) { 746 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 747 entry.getValue().post(new Runnable() { 748 @Override 749 public void run() { 750 entry.getKey().onShowSafetyWarning(flags); 751 } 752 }); 753 } 754 } 755 756 @Override onAccessibilityModeChanged(Boolean showA11yStream)757 public void onAccessibilityModeChanged(Boolean showA11yStream) { 758 boolean show = showA11yStream == null ? false : showA11yStream; 759 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 760 entry.getValue().post(new Runnable() { 761 @Override 762 public void run() { 763 entry.getKey().onAccessibilityModeChanged(show); 764 } 765 }); 766 } 767 } 768 } 769 770 771 private final class SettingObserver extends ContentObserver { 772 private final Uri ZEN_MODE_URI = 773 Settings.Global.getUriFor(Settings.Global.ZEN_MODE); 774 private final Uri ZEN_MODE_CONFIG_URI = 775 Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG); 776 SettingObserver(Handler handler)777 public SettingObserver(Handler handler) { 778 super(handler); 779 } 780 init()781 public void init() { 782 mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); 783 mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this); 784 } 785 destroy()786 public void destroy() { 787 mContext.getContentResolver().unregisterContentObserver(this); 788 } 789 790 @Override onChange(boolean selfChange, Uri uri)791 public void onChange(boolean selfChange, Uri uri) { 792 boolean changed = false; 793 if (ZEN_MODE_URI.equals(uri)) { 794 changed = updateZenModeW(); 795 } 796 if (changed) { 797 mCallbacks.onStateChanged(mState); 798 } 799 } 800 } 801 802 private final class Receiver extends BroadcastReceiver { 803 init()804 public void init() { 805 final IntentFilter filter = new IntentFilter(); 806 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 807 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); 808 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 809 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 810 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); 811 filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); 812 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 813 filter.addAction(Intent.ACTION_SCREEN_OFF); 814 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 815 mContext.registerReceiver(this, filter, null, mWorker); 816 } 817 destroy()818 public void destroy() { 819 mContext.unregisterReceiver(this); 820 } 821 822 @Override onReceive(Context context, Intent intent)823 public void onReceive(Context context, Intent intent) { 824 final String action = intent.getAction(); 825 boolean changed = false; 826 if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 827 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 828 final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); 829 final int oldLevel = intent 830 .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); 831 if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream 832 + " level=" + level + " oldLevel=" + oldLevel); 833 changed = updateStreamLevelW(stream, level); 834 } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { 835 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 836 final int devices = intent 837 .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1); 838 final int oldDevices = intent 839 .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1); 840 if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream=" 841 + stream + " devices=" + devices + " oldDevices=" + oldDevices); 842 changed = checkRoutedToBluetoothW(stream); 843 changed |= onVolumeChangedW(stream, 0); 844 } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 845 final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); 846 if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm=" 847 + Util.ringerModeToString(rm)); 848 changed = updateRingerModeExternalW(rm); 849 } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) { 850 final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); 851 if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm=" 852 + Util.ringerModeToString(rm)); 853 changed = updateRingerModeInternalW(rm); 854 } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { 855 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 856 final boolean muted = intent 857 .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); 858 if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream 859 + " muted=" + muted); 860 changed = updateStreamMuteW(stream, muted); 861 } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) { 862 if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED"); 863 changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); 864 } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 865 if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED"); 866 mCallbacks.onConfigurationChanged(); 867 } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { 868 if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF"); 869 mCallbacks.onScreenOff(); 870 } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) { 871 if (D.BUG) Log.d(TAG, "onReceive ACTION_CLOSE_SYSTEM_DIALOGS"); 872 dismiss(); 873 } 874 if (changed) { 875 mCallbacks.onStateChanged(mState); 876 } 877 } 878 } 879 880 private final class MediaSessionsCallbacks implements MediaSessions.Callbacks { 881 private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>(); 882 883 private int mNextStream = DYNAMIC_STREAM_START_INDEX; 884 885 @Override onRemoteUpdate(Token token, String name, PlaybackInfo pi)886 public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) { 887 if (!mRemoteStreams.containsKey(token)) { 888 mRemoteStreams.put(token, mNextStream); 889 if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream); 890 mNextStream++; 891 } 892 final int stream = mRemoteStreams.get(token); 893 boolean changed = mState.states.indexOfKey(stream) < 0; 894 final StreamState ss = streamStateW(stream); 895 ss.dynamic = true; 896 ss.levelMin = 0; 897 ss.levelMax = pi.getMaxVolume(); 898 if (ss.level != pi.getCurrentVolume()) { 899 ss.level = pi.getCurrentVolume(); 900 changed = true; 901 } 902 if (!Objects.equals(ss.remoteLabel, name)) { 903 ss.name = -1; 904 ss.remoteLabel = name; 905 changed = true; 906 } 907 if (changed) { 908 if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level 909 + " of " + ss.levelMax); 910 mCallbacks.onStateChanged(mState); 911 } 912 } 913 914 @Override 915 public void onRemoteVolumeChanged(Token token, int flags) { 916 final int stream = mRemoteStreams.get(token); 917 final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0; 918 boolean changed = updateActiveStreamW(stream); 919 if (showUI) { 920 changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC); 921 } 922 if (changed) { 923 mCallbacks.onStateChanged(mState); 924 } 925 if (showUI) { 926 mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED); 927 } 928 } 929 930 @Override 931 public void onRemoteRemoved(Token token) { 932 final int stream = mRemoteStreams.get(token); 933 mState.states.remove(stream); 934 if (mState.activeStream == stream) { 935 updateActiveStreamW(-1); 936 } 937 mCallbacks.onStateChanged(mState); 938 } 939 940 public void setStreamVolume(int stream, int level) { 941 final Token t = findToken(stream); 942 if (t == null) { 943 Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); 944 return; 945 } 946 mMediaSessions.setVolume(t, level); 947 } 948 949 private Token findToken(int stream) { 950 for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) { 951 if (entry.getValue().equals(stream)) { 952 return entry.getKey(); 953 } 954 } 955 return null; 956 } 957 } 958 959 public interface UserActivityListener { 960 void onUserActivity(); 961 } 962 } 963