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