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