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