• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityManager;
22 import android.app.KeyguardManager;
23 import android.app.NotificationManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.NameNotFoundException;
32 import android.database.ContentObserver;
33 import android.media.AudioAttributes;
34 import android.media.AudioManager;
35 import android.media.AudioSystem;
36 import android.media.IAudioService;
37 import android.media.IVolumeController;
38 import android.media.MediaRouter2Manager;
39 import android.media.VolumePolicy;
40 import android.net.Uri;
41 import android.os.Handler;
42 import android.os.HandlerExecutor;
43 import android.os.Looper;
44 import android.os.Message;
45 import android.os.RemoteException;
46 import android.os.VibrationEffect;
47 import android.provider.Settings;
48 import android.service.notification.Condition;
49 import android.service.notification.ZenModeConfig;
50 import android.util.ArrayMap;
51 import android.util.Log;
52 import android.util.Slog;
53 import android.view.accessibility.AccessibilityManager;
54 import android.view.accessibility.CaptioningManager;
55 
56 import androidx.annotation.NonNull;
57 import androidx.annotation.Nullable;
58 import androidx.lifecycle.Observer;
59 
60 import com.android.internal.annotations.GuardedBy;
61 import com.android.settingslib.volume.MediaSessions;
62 import com.android.settingslib.volume.MediaSessions.SessionId;
63 import com.android.systemui.Dumpable;
64 import com.android.systemui.Flags;
65 import com.android.systemui.broadcast.BroadcastDispatcher;
66 import com.android.systemui.dagger.SysUISingleton;
67 import com.android.systemui.dump.DumpManager;
68 import com.android.systemui.keyguard.WakefulnessLifecycle;
69 import com.android.systemui.plugins.VolumeDialogController;
70 import com.android.systemui.qs.tiles.DndTile;
71 import com.android.systemui.res.R;
72 import com.android.systemui.settings.UserTracker;
73 import com.android.systemui.statusbar.VibratorHelper;
74 import com.android.systemui.util.RingerModeLiveData;
75 import com.android.systemui.util.RingerModeTracker;
76 import com.android.systemui.util.concurrency.ThreadFactory;
77 import com.android.systemui.util.kotlin.JavaAdapter;
78 import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
79 import com.android.systemui.volume.shared.VolumeLogger;
80 
81 import dalvik.annotation.optimization.NeverCompile;
82 
83 import kotlin.Unit;
84 import kotlin.jvm.functions.Function1;
85 
86 import java.io.PrintWriter;
87 import java.util.HashMap;
88 import java.util.Map;
89 import java.util.Objects;
90 import java.util.concurrent.ConcurrentHashMap;
91 import java.util.concurrent.atomic.AtomicReference;
92 
93 import javax.inject.Inject;
94 
95 /**
96  *  Source of truth for all state / events related to the volume dialog.  No presentation.
97  *
98  *  All work done on a dedicated background worker thread & associated worker.
99  *
100  *  Methods ending in "W" must be called on the worker thread.
101  */
102 @SysUISingleton
103 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
104     private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
105     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
106 
107     private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
108     // We only need one dynamic stream for broadcast because at most two headsets are allowed
109     // to join local broadcast in current stage.
110     // It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
111     // streams defined in AudioSystem for now and audio team is in the middle of restructure,
112     // no new default stream is preferred.
113     public static final int DYNAMIC_STREAM_BROADCAST = 99;
114     public static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
115     private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
116             new AudioAttributes.Builder()
117                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
118                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
119                     .build();
120 
121     static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>();
122     static {
STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm)123         STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm);
STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf)124         STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music)125         STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility)126         STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification)127         STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification);
STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring)128         STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring);
STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system)129         STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system);
STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced)130         STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced);
STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts)131         STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts);
STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call)132         STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call);
133     }
134 
135     private final W mWorker;
136     private final Context mContext;
137     private final Looper mWorkerLooper;
138     private final PackageManager mPackageManager;
139     private final MediaRouter2Manager mRouter2Manager;
140     private final WakefulnessLifecycle mWakefulnessLifecycle;
141     private final AudioManager mAudio;
142     private final IAudioService mAudioService;
143     private final NotificationManager mNoMan;
144     private final SettingObserver mObserver;
145     private final Receiver mReceiver = new Receiver();
146     private final RingerModeObservers mRingerModeObservers;
147     private final MediaSessions mMediaSessions;
148     private final AtomicReference<CaptioningManager> mCaptioningManager = new AtomicReference<>();
149     private final KeyguardManager mKeyguardManager;
150     private final ActivityManager mActivityManager;
151     private final UserTracker mUserTracker;
152     private final VolumeControllerAdapter mVolumeControllerAdapter;
153     protected C mCallbacks = new C();
154     private final State mState = new State();
155     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
156     private final VibratorHelper mVibrator;
157     private final AudioSharingInteractor mAudioSharingInteractor;
158     private final JavaAdapter mJavaAdapter;
159     private final VolumeLogger mVolumeLogger;
160     private final boolean mHasVibrator;
161     private boolean mShowA11yStream;
162     private boolean mShowVolumeDialog;
163     private boolean mShowSafetyWarning;
164     private long mLastToggledRingerOn;
165     private boolean mDeviceInteractive = true;
166     boolean mInAudioSharing = false;
167 
168     private VolumePolicy mVolumePolicy;
169     @GuardedBy("this")
170     private UserActivityListener mUserActivityListener;
171 
172     protected final VC mVolumeController = new VC();
173     protected final BroadcastDispatcher mBroadcastDispatcher;
174 
175     private final WakefulnessLifecycle.Observer mWakefullnessLifecycleObserver =
176             new WakefulnessLifecycle.Observer() {
177                 @Override
178                 public void onStartedWakingUp() {
179                     mDeviceInteractive = true;
180                 }
181 
182                 @Override
183                 public void onFinishedGoingToSleep() {
184                     mDeviceInteractive = false;
185                 }
186             };
187 
188     @Inject
VolumeDialogControllerImpl( Context context, BroadcastDispatcher broadcastDispatcher, RingerModeTracker ringerModeTracker, ThreadFactory theadFactory, AudioManager audioManager, NotificationManager notificationManager, VibratorHelper vibrator, IAudioService iAudioService, VolumeControllerAdapter volumeControllerAdapter, AccessibilityManager accessibilityManager, PackageManager packageManager, WakefulnessLifecycle wakefulnessLifecycle, KeyguardManager keyguardManager, ActivityManager activityManager, UserTracker userTracker, DumpManager dumpManager, AudioSharingInteractor audioSharingInteractor, JavaAdapter javaAdapter, VolumeLogger volumeLogger )189     public VolumeDialogControllerImpl(
190             Context context,
191             BroadcastDispatcher broadcastDispatcher,
192             RingerModeTracker ringerModeTracker,
193             ThreadFactory theadFactory,
194             AudioManager audioManager,
195             NotificationManager notificationManager,
196             VibratorHelper vibrator,
197             IAudioService iAudioService,
198             VolumeControllerAdapter volumeControllerAdapter,
199             AccessibilityManager accessibilityManager,
200             PackageManager packageManager,
201             WakefulnessLifecycle wakefulnessLifecycle,
202             KeyguardManager keyguardManager,
203             ActivityManager activityManager,
204             UserTracker userTracker,
205             DumpManager dumpManager,
206             AudioSharingInteractor audioSharingInteractor,
207             JavaAdapter javaAdapter,
208             VolumeLogger volumeLogger
209     ) {
210         mContext = context.getApplicationContext();
211         mPackageManager = packageManager;
212         mWakefulnessLifecycle = wakefulnessLifecycle;
213         Events.writeEvent(Events.EVENT_COLLECTION_STARTED);
214         mWorkerLooper = theadFactory.buildLooperOnNewThread(
215                 VolumeDialogControllerImpl.class.getSimpleName());
216         mWorker = new W(mWorkerLooper);
217         mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
218         mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
219         mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
220         mAudioSharingInteractor = audioSharingInteractor;
221         mJavaAdapter = javaAdapter;
222         mVolumeLogger = volumeLogger;
223         mAudio = audioManager;
224         mNoMan = notificationManager;
225         mObserver = new SettingObserver(mWorker);
226         mRingerModeObservers = new RingerModeObservers(
227                 (RingerModeLiveData) ringerModeTracker.getRingerMode(),
228                 (RingerModeLiveData) ringerModeTracker.getRingerModeInternal()
229         );
230         mRingerModeObservers.init();
231         mBroadcastDispatcher = broadcastDispatcher;
232         mObserver.init();
233         mReceiver.init();
234         mVibrator = vibrator;
235         mHasVibrator = mVibrator.hasVibrator();
236         mAudioService = iAudioService;
237         mVolumeControllerAdapter = volumeControllerAdapter;
238         mKeyguardManager = keyguardManager;
239         mActivityManager = activityManager;
240         mUserTracker = userTracker;
241         mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mWorker));
242         createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
243 
244         dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
245 
246         boolean accessibilityVolumeStreamActive = accessibilityManager
247                 .isAccessibilityVolumeStreamActive();
248         mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
249                 VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
250                 VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
251 
252         mWakefulnessLifecycle.addObserver(mWakefullnessLifecycleObserver);
253     }
254 
getAudioManager()255     public AudioManager getAudioManager() {
256         return mAudio;
257     }
258 
dismiss()259     public void dismiss() {
260         mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
261     }
262 
setVolumeController()263     protected void setVolumeController() {
264         if (Flags.useVolumeController()) {
265             mVolumeControllerAdapter.collectToController(mVolumeController);
266         } else {
267             try {
268                 mAudio.setVolumeController(mVolumeController);
269             } catch (SecurityException e) {
270                 Log.w(TAG, "Unable to set the volume controller", e);
271             }
272         }
273     }
274 
setAudioManagerStreamVolume(int stream, int level, int flag)275     protected void setAudioManagerStreamVolume(int stream, int level, int flag) {
276         mAudio.setStreamVolume(stream, level, flag);
277     }
278 
getAudioManagerStreamVolume(int stream)279     protected int getAudioManagerStreamVolume(int stream) {
280         return mAudio.getLastAudibleStreamVolume(stream);
281     }
282 
getAudioManagerStreamMaxVolume(int stream)283     protected int getAudioManagerStreamMaxVolume(int stream) {
284         return mAudio.getStreamMaxVolume(stream);
285     }
286 
getAudioManagerStreamMinVolume(int stream)287     protected int getAudioManagerStreamMinVolume(int stream) {
288         return mAudio.getStreamMinVolumeInt(stream);
289     }
290 
register()291     public void register() {
292         setVolumeController();
293         setVolumePolicy(mVolumePolicy);
294         showDndTile();
295         try {
296             mMediaSessions.init();
297         } catch (SecurityException e) {
298             Log.w(TAG, "No access to media sessions", e);
299         }
300         Function1<Throwable, Unit> errorCallback = (ex) -> {
301             mVolumeLogger.onAudioSharingAvailabilityRequestedError("register()",
302                     ex.getMessage());
303             return null;
304         };
305         var unused =
306                 mJavaAdapter.<Context, Boolean>callSuspend(
307                         mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext,
308                         result -> {
309                             if (result) {
310                                 Slog.d(TAG, "Start collect volume changes in audio sharing");
311                                 mJavaAdapter.alwaysCollectFlow(
312                                         mAudioSharingInteractor.getVolume(),
313                                         volume -> handleAudioSharingStreamVolumeChanges(volume));
314                                 mJavaAdapter.alwaysCollectFlow(
315                                         mAudioSharingInteractor.isInAudioSharing(),
316                                         inSharing -> mInAudioSharing = inSharing);
317                             }
318                             return null;
319                         },
320                         errorCallback,
321                         errorCallback);
322     }
323 
setVolumePolicy(VolumePolicy policy)324     public void setVolumePolicy(VolumePolicy policy) {
325         mVolumePolicy = policy;
326         if (mVolumePolicy == null) return;
327         try {
328             mAudio.setVolumePolicy(mVolumePolicy);
329         } catch (NoSuchMethodError e) {
330             Log.w(TAG, "No volume policy api");
331         }
332     }
333 
createMediaSessions(Context context, Looper looper, MediaSessions.Callbacks callbacks)334     protected MediaSessions createMediaSessions(Context context, Looper looper,
335             MediaSessions.Callbacks callbacks) {
336         return new MediaSessions(context, looper, callbacks);
337     }
338 
339     @NeverCompile
dump(PrintWriter pw, String[] args)340     public void dump(PrintWriter pw, String[] args) {
341         pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:");
342         pw.print("  mVolumePolicy: "); pw.println(mVolumePolicy);
343         pw.print("  mState: "); pw.println(mState.toString(4));
344         pw.print("  mHasVibrator: "); pw.println(mHasVibrator);
345         synchronized (mMediaSessionsCallbacksW.mRemoteStreams) {
346             pw.print("  mRemoteStreams: ");
347             pw.println(mMediaSessionsCallbacksW.mRemoteStreams
348                     .values());
349         }
350         pw.print("  mShowA11yStream: "); pw.println(mShowA11yStream);
351         pw.println();
352         mMediaSessions.dump(pw);
353     }
354 
addCallback(Callbacks callback, Handler handler)355     public void addCallback(Callbacks callback, Handler handler) {
356         mCallbacks.add(callback, handler);
357         callback.onAccessibilityModeChanged(mShowA11yStream);
358     }
359 
setUserActivityListener(UserActivityListener listener)360     public void setUserActivityListener(UserActivityListener listener) {
361         synchronized (this) {
362             mUserActivityListener = listener;
363         }
364     }
365 
removeCallback(Callbacks callback)366     public void removeCallback(Callbacks callback) {
367         mCallbacks.remove(callback);
368     }
369 
getState()370     public void getState() {
371         mWorker.sendEmptyMessage(W.GET_STATE);
372     }
373 
374     /**
375      * We met issues about the wrong state of System Caption in multi-user mode.
376      * It happened in the usage of CaptioningManager Service from SysUI process
377      * that is a global system process of User 0.
378      * Therefore, we have to add callback on UserTracker that allows us to get the Context of
379      * active User and then get the corresponding CaptioningManager Service for further usages.
380      */
381     private final UserTracker.Callback mUserChangedCallback =
382             new UserTracker.Callback() {
383                 @Override
384                 public void onUserChanged(int newUser, @NonNull Context userContext) {
385                     createCaptioningManagerServiceByUserContext(userContext);
386                 }
387             };
388 
createCaptioningManagerServiceByUserContext(@onNull Context userContext)389     private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
390         mCaptioningManager.set(userContext.getSystemService(CaptioningManager.class));
391     }
392 
getCaptionsEnabledState(boolean checkForSwitchState)393     public void getCaptionsEnabledState(boolean checkForSwitchState) {
394         mWorker.obtainMessage(W.GET_CAPTIONS_ENABLED_STATE, checkForSwitchState).sendToTarget();
395     }
396 
setCaptionsEnabledState(boolean enabled)397     public void setCaptionsEnabledState(boolean enabled) {
398         mWorker.obtainMessage(W.SET_CAPTIONS_ENABLED_STATE, enabled).sendToTarget();
399     }
400 
getCaptionsComponentState(boolean fromTooltip)401     public void getCaptionsComponentState(boolean fromTooltip) {
402         mWorker.obtainMessage(W.GET_CAPTIONS_COMPONENT_STATE, fromTooltip).sendToTarget();
403     }
404 
notifyVisible(boolean visible)405     public void notifyVisible(boolean visible) {
406         mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
407     }
408 
userActivity()409     public void userActivity() {
410         mWorker.removeMessages(W.USER_ACTIVITY);
411         mWorker.sendEmptyMessage(W.USER_ACTIVITY);
412     }
413 
setRingerMode(int value, boolean external)414     public void setRingerMode(int value, boolean external) {
415         mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget();
416     }
417 
setZenMode(int value)418     public void setZenMode(int value) {
419         mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
420     }
421 
setExitCondition(Condition condition)422     public void setExitCondition(Condition condition) {
423         mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
424     }
425 
setStreamMute(int stream, boolean mute)426     public void setStreamMute(int stream, boolean mute) {
427         mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget();
428     }
429 
setStreamVolume(int stream, int level)430     public void setStreamVolume(int stream, int level) {
431         mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
432     }
433 
setActiveStream(int stream)434     public void setActiveStream(int stream) {
435         mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
436     }
437 
setEnableDialogs(boolean volumeUi, boolean safetyWarning)438     public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
439         mShowVolumeDialog = volumeUi;
440         mShowSafetyWarning = safetyWarning;
441     }
442 
443     @Override
scheduleTouchFeedback()444     public void scheduleTouchFeedback() {
445         mLastToggledRingerOn = System.currentTimeMillis();
446     }
447 
playTouchFeedback()448     private void playTouchFeedback() {
449         if (System.currentTimeMillis() - mLastToggledRingerOn < TOUCH_FEEDBACK_TIMEOUT_MS) {
450             try {
451                 mAudioService.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD,
452                         mUserTracker.getUserId());
453             } catch (RemoteException e) {
454                 // ignore
455             }
456         }
457     }
458 
vibrate(VibrationEffect effect)459     public void vibrate(VibrationEffect effect) {
460         mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES);
461     }
462 
hasVibrator()463     public boolean hasVibrator() {
464         return mHasVibrator;
465     }
466 
onNotifyVisibleW(boolean visible)467     private void onNotifyVisibleW(boolean visible) {
468         if (Flags.useVolumeController()) {
469             mVolumeControllerAdapter.notifyVolumeControllerVisible(visible);
470         } else {
471             mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
472         }
473         if (!visible) {
474             if (updateActiveStreamW(-1)) {
475                 mCallbacks.onStateChanged(mState);
476             }
477         }
478     }
479 
onUserActivityW()480     private void onUserActivityW() {
481         synchronized (this) {
482             if (mUserActivityListener != null) {
483                 mUserActivityListener.onUserActivity();
484             }
485         }
486     }
487 
onShowSafetyWarningW(int flags)488     private void onShowSafetyWarningW(int flags) {
489         if (mShowSafetyWarning) {
490             mCallbacks.onShowSafetyWarning(flags);
491         }
492     }
493 
onShowCsdWarningW(@udioManager.CsdWarning int csdWarning, int durationMs)494     private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
495         mCallbacks.onShowCsdWarning(csdWarning, durationMs);
496     }
497 
onGetCaptionsComponentStateW(boolean fromTooltip)498     private void onGetCaptionsComponentStateW(boolean fromTooltip) {
499         CaptioningManager captioningManager = mCaptioningManager.get();
500         if (null != captioningManager) {
501             mCallbacks.onCaptionComponentStateChanged(
502                     captioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
503         } else {
504             Log.e(TAG, "onGetCaptionsComponentStateW(), null captioningManager");
505         }
506     }
507 
onGetCaptionsEnabledStateW(boolean checkForSwitchState)508     private void onGetCaptionsEnabledStateW(boolean checkForSwitchState) {
509         CaptioningManager captioningManager = mCaptioningManager.get();
510         if (null != captioningManager) {
511             mCallbacks.onCaptionEnabledStateChanged(
512                     captioningManager.isSystemAudioCaptioningEnabled(), checkForSwitchState);
513         } else {
514             Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
515         }
516     }
517 
onSetCaptionsEnabledStateW(boolean enabled)518     private void onSetCaptionsEnabledStateW(boolean enabled) {
519         CaptioningManager captioningManager = mCaptioningManager.get();
520         if (null != captioningManager) {
521             captioningManager.setSystemAudioCaptioningEnabled(enabled);
522             mCallbacks.onCaptionEnabledStateChanged(
523                     captioningManager.isSystemAudioCaptioningEnabled(), false);
524         } else {
525             Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
526         }
527     }
528 
onAccessibilityModeChanged(Boolean showA11yStream)529     private void onAccessibilityModeChanged(Boolean showA11yStream) {
530         mCallbacks.onAccessibilityModeChanged(showA11yStream);
531     }
532 
checkRoutedToBluetoothW(int stream)533     private boolean checkRoutedToBluetoothW(int stream) {
534         boolean changed = false;
535         if (stream == AudioManager.STREAM_MUSIC) {
536             // Note: Here we didn't use DEVICE_OUT_BLE_SPEAKER and DEVICE_OUT_BLE_BROADCAST
537             //       Since their values overlap with DEVICE_OUT_EARPIECE and DEVICE_OUT_SPEAKER.
538             //       Anyway, we can check BLE devices by using just DEVICE_OUT_BLE_HEADSET.
539             final boolean routedToBluetooth =
540                     // TODO(b/359737651): Need audio support to return broadcast mask.
541                     // For now, mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) will return
542                     // AudioManager.DEVICE_NONE, so we also need to check if the device is in audio
543                     // sharing here.
544                     mInAudioSharing
545                             || (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC)
546                                             & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
547                                                     | AudioManager
548                                                             .DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
549                                                     | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER
550                                                     | AudioManager.DEVICE_OUT_BLE_HEADSET))
551                                     != 0;
552             changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
553         } else if (stream == AudioManager.STREAM_VOICE_CALL) {
554             final int devices = mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL);
555             final int bluetoothDevicesMask = (AudioManager.DEVICE_OUT_BLE_HEADSET
556                     | AudioManager.DEVICE_OUT_BLUETOOTH_SCO_HEADSET
557                     | AudioManager.DEVICE_OUT_BLUETOOTH_SCO_CARKIT);
558             changed |= updateStreamRoutedToBluetoothW(stream,
559                     (devices & bluetoothDevicesMask) != 0);
560         }
561         return changed;
562     }
563 
shouldShowUI(int flags)564     private boolean shouldShowUI(int flags) {
565         int wakefulness = mWakefulnessLifecycle.getWakefulness();
566         return wakefulness != WakefulnessLifecycle.WAKEFULNESS_ASLEEP
567                 && wakefulness != WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP
568                 && mDeviceInteractive && (flags & AudioManager.FLAG_SHOW_UI) != 0
569                 && mShowVolumeDialog;
570     }
571 
onVolumeChangedW(int stream, int flags, boolean sendChanges)572     boolean onVolumeChangedW(int stream, int flags, boolean sendChanges) {
573         final boolean showUI = shouldShowUI(flags);
574         final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
575         final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
576         final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
577         boolean changed = false;
578         if (showUI) {
579             changed |= updateActiveStreamW(stream);
580         }
581         int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
582         changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
583         changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
584         if (changed && sendChanges) {
585             mCallbacks.onStateChanged(mState);
586         }
587         if (showUI) {
588             onShowRequestedW(Events.SHOW_REASON_VOLUME_CHANGED);
589         }
590         if (showVibrateHint) {
591             mCallbacks.onShowVibrateHint();
592         }
593         if (showSilentHint) {
594             mCallbacks.onShowSilentHint();
595         }
596         if (changed && fromKey) {
597             Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);
598             mCallbacks.onVolumeChangedFromKey();
599         }
600         return changed;
601     }
602 
updateActiveStreamW(int activeStream)603     private boolean updateActiveStreamW(int activeStream) {
604         if (activeStream == mState.activeStream) return false;
605         mState.activeStream = activeStream;
606         Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
607         if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
608         Function1<Throwable, Unit> errorCallback = (ex) -> {
609             mVolumeLogger.onAudioSharingAvailabilityRequestedError(
610                     "updateActiveStreamW",
611                     ex.getMessage());
612             forceVolumeControlStreamW(activeStream, false);
613             return null;
614         };
615         var unused =
616                 mJavaAdapter.<Context, Boolean>callSuspend(
617                         mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext,
618                         result -> {
619                             forceVolumeControlStreamW(activeStream, result);
620                             return null;
621                         },
622                         errorCallback,
623                         errorCallback);
624         return true;
625     }
626 
forceVolumeControlStreamW(int activeStream, boolean audioSharingVolumeBarAvailable)627     private void forceVolumeControlStreamW(int activeStream,
628             boolean audioSharingVolumeBarAvailable) {
629         final int dynamicStartIdx = audioSharingVolumeBarAvailable ? DYNAMIC_STREAM_BROADCAST
630                 : DYNAMIC_STREAM_REMOTE_START_INDEX;
631         final int s = activeStream < dynamicStartIdx ? activeStream : -1;
632         if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
633         mWorker.post(() -> mAudio.forceVolumeControlStream(s));
634     }
635 
streamStateW(int stream)636     private StreamState streamStateW(int stream) {
637         StreamState ss = mState.states.get(stream);
638         if (ss == null) {
639             ss = new StreamState();
640             mState.states.put(stream, ss);
641         }
642         return ss;
643     }
644 
onGetStateW()645     private void onGetStateW() {
646         for (int stream : STREAMS.keySet()) {
647             updateStreamLevelW(stream, getAudioManagerStreamVolume(stream));
648             streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream);
649             streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream));
650             updateStreamMuteW(stream, mAudio.isStreamMute(stream));
651             final StreamState ss = streamStateW(stream);
652             ss.muteSupported = mAudio.isStreamMutableByUi(stream);
653             ss.name = STREAMS.get(stream);
654             checkRoutedToBluetoothW(stream);
655         }
656         // We are not destroyed so this is listening and has updated information
657         updateRingerModeExternalW(mRingerModeObservers.mRingerMode.getValue());
658         updateZenModeW();
659         updateZenConfig();
660         updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
661         mCallbacks.onStateChanged(mState);
662     }
663 
updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth)664     private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) {
665         final StreamState ss = streamStateW(stream);
666         if (ss.routedToBluetooth == routedToBluetooth) return false;
667         ss.routedToBluetooth = routedToBluetooth;
668         if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream
669                 + " routedToBluetooth=" + routedToBluetooth);
670         return true;
671     }
672 
updateStreamLevelW(int stream, int level)673     private boolean updateStreamLevelW(int stream, int level) {
674         final StreamState ss = streamStateW(stream);
675         if (ss.level == level) return false;
676         ss.level = level;
677         if (isLogWorthy(stream)) {
678             Events.writeEvent(Events.EVENT_LEVEL_CHANGED, stream, level);
679         }
680         return true;
681     }
682 
isLogWorthy(int stream)683     private static boolean isLogWorthy(int stream) {
684         switch (stream) {
685             case AudioSystem.STREAM_ALARM:
686             case AudioSystem.STREAM_MUSIC:
687             case AudioSystem.STREAM_RING:
688             case AudioSystem.STREAM_SYSTEM:
689             case AudioSystem.STREAM_VOICE_CALL:
690                 return true;
691         }
692         return false;
693     }
694 
updateStreamMuteW(int stream, boolean muted)695     private boolean updateStreamMuteW(int stream, boolean muted) {
696         final StreamState ss = streamStateW(stream);
697         if (ss.muted == muted) return false;
698         ss.muted = muted;
699         if (isLogWorthy(stream)) {
700             Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted);
701         }
702         if (muted && isRinger(stream)) {
703             updateRingerModeInternalW(mRingerModeObservers.mRingerModeInternal.getValue());
704         }
705         return true;
706     }
707 
isRinger(int stream)708     private static boolean isRinger(int stream) {
709         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
710     }
711 
updateEffectsSuppressorW(ComponentName effectsSuppressor)712     private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
713         if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
714         mState.effectsSuppressor = effectsSuppressor;
715         mState.effectsSuppressorName =
716                 getApplicationName(mPackageManager, mState.effectsSuppressor);
717         Events.writeEvent(Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor,
718                 mState.effectsSuppressorName);
719         return true;
720     }
721 
getApplicationName(PackageManager pm, ComponentName component)722     private static String getApplicationName(PackageManager pm, ComponentName component) {
723         if (component == null) return null;
724         final String pkg = component.getPackageName();
725         try {
726             final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
727             final String rt = Objects.toString(ai.loadLabel(pm), "").trim();
728             if (rt.length() > 0) {
729                 return rt;
730             }
731         } catch (NameNotFoundException e) {}
732         return pkg;
733     }
734 
updateZenModeW()735     private boolean updateZenModeW() {
736         final int zen = Settings.Global.getInt(mContext.getContentResolver(),
737                 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
738         if (mState.zenMode == zen) return false;
739         mState.zenMode = zen;
740         Events.writeEvent(Events.EVENT_ZEN_MODE_CHANGED, zen);
741         return true;
742     }
743 
updateZenConfig()744     private boolean updateZenConfig() {
745         final NotificationManager.Policy policy = mNoMan.getConsolidatedNotificationPolicy();
746         boolean disallowAlarms = (policy.priorityCategories & NotificationManager.Policy
747                 .PRIORITY_CATEGORY_ALARMS) == 0;
748         boolean disallowMedia = (policy.priorityCategories & NotificationManager.Policy
749                 .PRIORITY_CATEGORY_MEDIA) == 0;
750         boolean disallowSystem = (policy.priorityCategories & NotificationManager.Policy
751                 .PRIORITY_CATEGORY_SYSTEM) == 0;
752         // ringer controls notifications, ringer and system sounds, so only disallow ringer changes
753         // if all relevant (notifications + ringer + system) sounds are not allowed to bypass DND
754         boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(policy);
755         if (mState.disallowAlarms == disallowAlarms
756                 && mState.disallowMedia == disallowMedia
757                 && mState.disallowRinger == disallowRinger
758                 && mState.disallowSystem == disallowSystem) {
759             return false;
760         }
761         mState.disallowAlarms = disallowAlarms;
762         mState.disallowMedia = disallowMedia;
763         mState.disallowSystem = disallowSystem;
764         mState.disallowRinger = disallowRinger;
765         Events.writeEvent(Events.EVENT_ZEN_CONFIG_CHANGED, "disallowAlarms="
766                 + disallowAlarms + " disallowMedia=" + disallowMedia + " disallowSystem="
767                 + disallowSystem + " disallowRinger=" + disallowRinger);
768         return true;
769     }
770 
updateRingerModeExternalW(int rm)771     private boolean updateRingerModeExternalW(int rm) {
772         if (rm == mState.ringerModeExternal) return false;
773         mState.ringerModeExternal = rm;
774         Events.writeEvent(Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm);
775         return true;
776     }
777 
updateRingerModeInternalW(int rm)778     private boolean updateRingerModeInternalW(int rm) {
779         if (rm == mState.ringerModeInternal) return false;
780         mState.ringerModeInternal = rm;
781         Events.writeEvent(Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm);
782 
783         if (mState.ringerModeInternal == RINGER_MODE_NORMAL) {
784             playTouchFeedback();
785         }
786 
787         return true;
788     }
789 
onShowRequestedW(int reason)790     private void onShowRequestedW(int reason) {
791         mCallbacks.onShowRequested(reason, mKeyguardManager.isKeyguardLocked(),
792                 mActivityManager.getLockTaskModeState());
793     }
794 
onSetRingerModeW(int mode, boolean external)795     private void onSetRingerModeW(int mode, boolean external) {
796         if (external) {
797             mAudio.setRingerMode(mode);
798         } else {
799             mAudio.setRingerModeInternal(mode);
800         }
801     }
802 
onSetStreamMuteW(int stream, boolean mute)803     private void onSetStreamMuteW(int stream, boolean mute) {
804         mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
805                 : AudioManager.ADJUST_UNMUTE, 0);
806     }
807 
onSetStreamVolumeW(int stream, int level)808     private void onSetStreamVolumeW(int stream, int level) {
809         if (D.BUG) Log.d(TAG, "onSetStreamVolumeW " + stream + " level=" + level);
810 
811         if (stream == DYNAMIC_STREAM_BROADCAST) {
812             Function1<Throwable, Unit> errorCallback = (ex) -> {
813                 mVolumeLogger.onAudioSharingAvailabilityRequestedError(
814                         "onSetStreamVolumeW",
815                         ex.getMessage());
816                 return null;
817             };
818             var unused =
819                     mJavaAdapter.<Context, Boolean>callSuspend(
820                             mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext,
821                             result -> {
822                                 if (result) {
823                                     mAudioSharingInteractor.setStreamVolume(level);
824                                 }
825                                 return null;
826                             },
827                             errorCallback,
828                             errorCallback);
829             return;
830         }
831         if (stream >= DYNAMIC_STREAM_REMOTE_START_INDEX) {
832             mMediaSessionsCallbacksW.setStreamVolume(stream, level);
833             return;
834         }
835         setAudioManagerStreamVolume(stream, level, 0);
836     }
837 
onSetActiveStreamW(int stream)838     private void onSetActiveStreamW(int stream) {
839         boolean changed = updateActiveStreamW(stream);
840         if (changed) {
841             mCallbacks.onStateChanged(mState);
842         }
843     }
844 
onSetExitConditionW(Condition condition)845     private void onSetExitConditionW(Condition condition) {
846         mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
847     }
848 
onSetZenModeW(int mode)849     private void onSetZenModeW(int mode) {
850         if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
851         mNoMan.setZenMode(mode, null, TAG);
852     }
853 
onDismissRequestedW(int reason)854     private void onDismissRequestedW(int reason) {
855         mCallbacks.onDismissRequested(reason);
856     }
857 
showDndTile()858     public void showDndTile() {
859         if (D.BUG) Log.d(TAG, "showDndTile");
860         DndTile.setVisible(mContext, true);
861     }
862 
handleAudioSharingStreamVolumeChanges(@ullable Integer volume)863     void handleAudioSharingStreamVolumeChanges(@Nullable Integer volume) {
864         if (volume == null) {
865             if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
866                 mState.states.remove(DYNAMIC_STREAM_BROADCAST);
867                 Slog.d(TAG, "Remove audio sharing stream");
868                 mCallbacks.onStateChanged(mState);
869             }
870         } else {
871             if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
872                 StreamState ss = mState.states.get(DYNAMIC_STREAM_BROADCAST);
873                 if (ss.level != volume) {
874                     ss.level = volume;
875                     Slog.d(TAG, "updateState, audio sharing stream volume = " + volume);
876                     mCallbacks.onStateChanged(mState);
877                 }
878             } else {
879                 StreamState ss = streamStateW(DYNAMIC_STREAM_BROADCAST);
880                 ss.dynamic = true;
881                 ss.levelMin = mAudioSharingInteractor.getVolumeMin();
882                 ss.levelMax = mAudioSharingInteractor.getVolumeMax();
883                 ss.routedToBluetooth = true;
884                 if (ss.level != volume) {
885                     ss.level = volume;
886                 }
887                 String label = mContext.getString(R.string.audio_sharing_description);
888                 if (!Objects.equals(ss.remoteLabel, label)) {
889                     ss.name = -1;
890                     ss.remoteLabel = label;
891                 }
892                 Slog.d(TAG, "updateState, new audio sharing stream volume = " + volume);
893                 mCallbacks.onStateChanged(mState);
894             }
895         }
896     }
897 
898     private final class VC extends IVolumeController.Stub {
899         private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";
900 
901         @Override
displaySafeVolumeWarning(int flags)902         public void displaySafeVolumeWarning(int flags) throws RemoteException {
903             if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning "
904                     + Util.audioManagerFlagsToString(flags));
905             mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget();
906         }
907 
908         /**
909          * Display a sound-dose related warning.
910          * This method will never be called if the CSD (Computed Sound Dose) feature is
911          * not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
912          * the feature.
913          * @param csdWarning the type of warning to display, values are one of
914          *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
915          *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
916          *        {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
917          *        {@link android.media.AudioManager#CSD_WARNING_ACCUMULATION_START}.
918          * @param displayDurationMs the time expressed in milliseconds after which the dialog will be
919          *        automatically dismissed, or -1 if there is no automatic timeout.
920          */
921         @Override
displayCsdWarning(int csdWarning, int displayDurationMs)922         public void displayCsdWarning(int csdWarning, int displayDurationMs) throws RemoteException
923         {
924             if (D.BUG) Log.d(TAG, "displayCsdWarning durMs=" + displayDurationMs);
925             mWorker.obtainMessage(W.SHOW_CSD_WARNING, csdWarning, displayDurationMs)
926                     .sendToTarget();
927         }
928 
929         @Override
volumeChanged(int streamType, int flags)930         public void volumeChanged(int streamType, int flags) throws RemoteException {
931             if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
932                     + " " + Util.audioManagerFlagsToString(flags));
933             mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
934         }
935 
936         @Override
masterMuteChanged(int flags)937         public void masterMuteChanged(int flags) throws RemoteException {
938             if (D.BUG) Log.d(TAG, "masterMuteChanged");
939         }
940 
941         @Override
setLayoutDirection(int layoutDirection)942         public void setLayoutDirection(int layoutDirection) throws RemoteException {
943             if (D.BUG) Log.d(TAG, "setLayoutDirection");
944             mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget();
945         }
946 
947         @Override
dismiss()948         public void dismiss() throws RemoteException {
949             if (D.BUG) Log.d(TAG, "dismiss requested");
950             mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0)
951                     .sendToTarget();
952             mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
953         }
954 
955         @Override
setA11yMode(int mode)956         public void setA11yMode(int mode) {
957             if (D.BUG) Log.d(TAG, "setA11yMode to " + mode);
958             switch (mode) {
959                 case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME:
960                     // "legacy" mode
961                     mShowA11yStream = false;
962                     break;
963                 case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME:
964                     mShowA11yStream = true;
965                     break;
966                 default:
967                     Log.e(TAG, "Invalid accessibility mode " + mode);
968                     break;
969             }
970             mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget();
971         }
972     }
973 
974     private final class W extends Handler {
975         private static final int VOLUME_CHANGED = 1;
976         private static final int DISMISS_REQUESTED = 2;
977         private static final int GET_STATE = 3;
978         private static final int SET_RINGER_MODE = 4;
979         private static final int SET_ZEN_MODE = 5;
980         private static final int SET_EXIT_CONDITION = 6;
981         private static final int SET_STREAM_MUTE = 7;
982         private static final int LAYOUT_DIRECTION_CHANGED = 8;
983         private static final int CONFIGURATION_CHANGED = 9;
984         private static final int SET_STREAM_VOLUME = 10;
985         private static final int SET_ACTIVE_STREAM = 11;
986         private static final int NOTIFY_VISIBLE = 12;
987         private static final int USER_ACTIVITY = 13;
988         private static final int SHOW_SAFETY_WARNING = 14;
989         private static final int ACCESSIBILITY_MODE_CHANGED = 15;
990         private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
991         private static final int SHOW_CSD_WARNING = 17;
992         private static final int GET_CAPTIONS_ENABLED_STATE = 18;
993         private static final int SET_CAPTIONS_ENABLED_STATE = 19;
994 
W(Looper looper)995         W(Looper looper) {
996             super(looper);
997         }
998 
999         @Override
handleMessage(Message msg)1000         public void handleMessage(Message msg) {
1001             switch (msg.what) {
1002                 case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2, true); break;
1003                 case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
1004                 case GET_STATE: onGetStateW(); break;
1005                 case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
1006                 case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
1007                 case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
1008                 case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
1009                 case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
1010                 case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
1011                 case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
1012                 case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
1013                 case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
1014                 case USER_ACTIVITY: onUserActivityW(); break;
1015                 case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
1016                 case GET_CAPTIONS_COMPONENT_STATE:
1017                     onGetCaptionsComponentStateW((Boolean) msg.obj); break;
1018                 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
1019                     break;
1020                 case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
1021                 case GET_CAPTIONS_ENABLED_STATE:
1022                     onGetCaptionsEnabledStateW((Boolean) msg.obj); break;
1023                 case SET_CAPTIONS_ENABLED_STATE:
1024                     onSetCaptionsEnabledStateW((Boolean) msg.obj); break;
1025             }
1026         }
1027     }
1028 
1029     static class C implements Callbacks {
1030         private final Map<Callbacks, Handler> mCallbackMap = new ConcurrentHashMap<>();
1031 
add(Callbacks callback, Handler handler)1032         public void add(Callbacks callback, Handler handler) {
1033             if (callback == null || handler == null) throw new IllegalArgumentException();
1034             mCallbackMap.put(callback, handler);
1035         }
1036 
remove(Callbacks callback)1037         public void remove(Callbacks callback) {
1038             mCallbackMap.remove(callback);
1039         }
1040 
1041         @Override
onShowRequested( final int reason, final boolean keyguardLocked, final int lockTaskModeState)1042         public void onShowRequested(
1043                 final int reason,
1044                 final boolean keyguardLocked,
1045                 final int lockTaskModeState) {
1046             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1047                 entry.getValue().post(new Runnable() {
1048                     @Override
1049                     public void run() {
1050                         entry.getKey().onShowRequested(reason, keyguardLocked, lockTaskModeState);
1051                     }
1052                 });
1053             }
1054         }
1055 
1056         @Override
onDismissRequested(final int reason)1057         public void onDismissRequested(final int reason) {
1058             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1059                 entry.getValue().post(new Runnable() {
1060                     @Override
1061                     public void run() {
1062                         entry.getKey().onDismissRequested(reason);
1063                     }
1064                 });
1065             }
1066         }
1067 
1068         @Override
onStateChanged(final State state)1069         public void onStateChanged(final State state) {
1070             final long time = System.currentTimeMillis();
1071             final State copy = state.copy();
1072             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1073                 entry.getValue().post(new Runnable() {
1074                     @Override
1075                     public void run() {
1076                         entry.getKey().onStateChanged(copy);
1077                     }
1078                 });
1079             }
1080             Events.writeState(time, copy);
1081         }
1082 
1083         @Override
onLayoutDirectionChanged(final int layoutDirection)1084         public void onLayoutDirectionChanged(final int layoutDirection) {
1085             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1086                 entry.getValue().post(new Runnable() {
1087                     @Override
1088                     public void run() {
1089                         entry.getKey().onLayoutDirectionChanged(layoutDirection);
1090                     }
1091                 });
1092             }
1093         }
1094 
1095         @Override
onConfigurationChanged()1096         public void onConfigurationChanged() {
1097             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1098                 entry.getValue().post(new Runnable() {
1099                     @Override
1100                     public void run() {
1101                         entry.getKey().onConfigurationChanged();
1102                     }
1103                 });
1104             }
1105         }
1106 
1107         @Override
onShowVibrateHint()1108         public void onShowVibrateHint() {
1109             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1110                 entry.getValue().post(new Runnable() {
1111                     @Override
1112                     public void run() {
1113                         entry.getKey().onShowVibrateHint();
1114                     }
1115                 });
1116             }
1117         }
1118 
1119         @Override
onShowSilentHint()1120         public void onShowSilentHint() {
1121             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1122                 entry.getValue().post(new Runnable() {
1123                     @Override
1124                     public void run() {
1125                         entry.getKey().onShowSilentHint();
1126                     }
1127                 });
1128             }
1129         }
1130 
1131         @Override
onScreenOff()1132         public void onScreenOff() {
1133             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1134                 entry.getValue().post(new Runnable() {
1135                     @Override
1136                     public void run() {
1137                         entry.getKey().onScreenOff();
1138                     }
1139                 });
1140             }
1141         }
1142 
1143         @Override
onShowSafetyWarning(final int flags)1144         public void onShowSafetyWarning(final int flags) {
1145             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1146                 entry.getValue().post(new Runnable() {
1147                     @Override
1148                     public void run() {
1149                         entry.getKey().onShowSafetyWarning(flags);
1150                     }
1151                 });
1152             }
1153         }
1154 
1155         @Override
onShowCsdWarning(int csdWarning, int durationMs)1156         public void onShowCsdWarning(int csdWarning, int durationMs) {
1157             if (Callbacks.VERSION < 2) {
1158                 return;
1159             }
1160             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1161                 entry.getValue().post(new Runnable() {
1162                     @Override
1163                     public void run() {
1164                         entry.getKey().onShowCsdWarning(csdWarning, durationMs);
1165                     }
1166                 });
1167             }
1168         }
1169 
1170         @Override
onVolumeChangedFromKey()1171         public void onVolumeChangedFromKey() {
1172             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1173                 entry.getValue().post(new Runnable() {
1174                     @Override
1175                     public void run() {
1176                         entry.getKey().onVolumeChangedFromKey();
1177                     }
1178                 });
1179             }
1180         }
1181 
1182         @Override
onAccessibilityModeChanged(Boolean showA11yStream)1183         public void onAccessibilityModeChanged(Boolean showA11yStream) {
1184             boolean show = showA11yStream != null && showA11yStream;
1185             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1186                 entry.getValue().post(new Runnable() {
1187                     @Override
1188                     public void run() {
1189                         entry.getKey().onAccessibilityModeChanged(show);
1190                     }
1191                 });
1192             }
1193         }
1194 
1195         @Override
onCaptionComponentStateChanged( Boolean isComponentEnabled, Boolean fromTooltip)1196         public void onCaptionComponentStateChanged(
1197                 Boolean isComponentEnabled, Boolean fromTooltip) {
1198             boolean componentEnabled = isComponentEnabled != null && isComponentEnabled;
1199             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1200                 entry.getValue().post(
1201                         () -> entry.getKey().onCaptionComponentStateChanged(
1202                                 componentEnabled, fromTooltip));
1203             }
1204         }
1205 
1206         @Override
onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch)1207         public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch) {
1208             boolean captionsEnabled = isEnabled != null && isEnabled;
1209             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
1210                 entry.getValue().post(
1211                         () -> entry.getKey().onCaptionEnabledStateChanged(
1212                                 captionsEnabled, checkBeforeSwitch));
1213             }
1214         }
1215 
1216     }
1217 
1218     private final class RingerModeObservers {
1219 
1220         private final RingerModeLiveData mRingerMode;
1221         private final RingerModeLiveData mRingerModeInternal;
1222 
1223         private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() {
1224             @Override
1225             public void onChanged(Integer value) {
1226                 mWorker.post(() -> {
1227                             final int rm = value;
1228                             if (mRingerMode.getInitialSticky()) {
1229                                 mState.ringerModeExternal = rm;
1230                             }
1231                             if (D.BUG) {
1232                                 Log.d(TAG, "onChange ringer_mode rm="
1233                                         + Util.ringerModeToString(rm));
1234                             }
1235                             if (updateRingerModeExternalW(rm)) {
1236                                 mCallbacks.onStateChanged(mState);
1237                             }
1238                         }
1239                 );
1240             }
1241         };
1242 
1243         private final Observer<Integer> mRingerModeInternalObserver = new Observer<Integer>() {
1244             @Override
1245             public void onChanged(Integer value) {
1246                 mWorker.post(() -> {
1247                             final int rm = value;
1248                             if (mRingerModeInternal.getInitialSticky()) {
1249                                 mState.ringerModeInternal = rm;
1250                             }
1251                             if (D.BUG) {
1252                                 Log.d(TAG, "onChange internal_ringer_mode rm="
1253                                         + Util.ringerModeToString(rm));
1254                             }
1255                             if (updateRingerModeInternalW(rm)) {
1256                                 mCallbacks.onStateChanged(mState);
1257                             }
1258                         }
1259                 );
1260             }
1261         };
1262 
RingerModeObservers(RingerModeLiveData ringerMode, RingerModeLiveData ringerModeInternal)1263         RingerModeObservers(RingerModeLiveData ringerMode,
1264                 RingerModeLiveData ringerModeInternal) {
1265             mRingerMode = ringerMode;
1266             mRingerModeInternal = ringerModeInternal;
1267         }
1268 
init()1269         public void init() {
1270             int initialValue = mRingerMode.getValue();
1271             if (initialValue != -1) {
1272                 // If it's not -1, set it to the initial value, if it's -1, it means that the
1273                 // tracker is not listening already and will obtain the sticky value.
1274                 mState.ringerModeExternal = initialValue;
1275             }
1276             mRingerMode.observeForever(mRingerModeObserver);
1277             initialValue = mRingerModeInternal.getValue();
1278             if (initialValue != -1) {
1279                 // If it's not -1, set it to the initial value, if it's -1, it means that the
1280                 // tracker is not listening already and will obtain the sticky value.
1281                 mState.ringerModeInternal = initialValue;
1282             }
1283             mRingerModeInternal.observeForever(mRingerModeInternalObserver);
1284         }
1285 
destroy()1286         public void destroy() {
1287             mRingerMode.removeObserver(mRingerModeObserver);
1288             mRingerModeInternal.removeObserver(mRingerModeInternalObserver);
1289         }
1290     }
1291 
1292     private final class SettingObserver extends ContentObserver {
1293         private final Uri ZEN_MODE_URI =
1294                 Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
1295         private final Uri ZEN_MODE_CONFIG_URI =
1296                 Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
1297 
SettingObserver(Handler handler)1298         public SettingObserver(Handler handler) {
1299             super(handler);
1300         }
1301 
init()1302         public void init() {
1303             mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
1304             mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
1305         }
1306 
destroy()1307         public void destroy() {
1308             mContext.getContentResolver().unregisterContentObserver(this);
1309         }
1310 
1311         @Override
onChange(boolean selfChange, Uri uri)1312         public void onChange(boolean selfChange, Uri uri) {
1313             boolean changed = false;
1314             if (ZEN_MODE_URI.equals(uri)) {
1315                 changed = updateZenModeW();
1316             }
1317             if (ZEN_MODE_CONFIG_URI.equals(uri)) {
1318                 changed |= updateZenConfig();
1319             }
1320 
1321             if (changed) {
1322                 mCallbacks.onStateChanged(mState);
1323             }
1324         }
1325     }
1326 
1327     private final class Receiver extends BroadcastReceiver {
1328 
1329         private static final int STREAM_UNKNOWN = -1;
1330 
init()1331         public void init() {
1332             final IntentFilter filter = new IntentFilter();
1333             filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
1334             filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
1335             filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
1336             filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
1337             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
1338             filter.addAction(Intent.ACTION_SCREEN_OFF);
1339             filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1340             mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
1341         }
1342 
destroy()1343         public void destroy() {
1344             mBroadcastDispatcher.unregisterReceiver(this);
1345         }
1346 
1347         @Override
onReceive(Context context, Intent intent)1348         public void onReceive(Context context, Intent intent) {
1349             final String action = intent.getAction();
1350             boolean changed = false;
1351             if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
1352                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
1353                         STREAM_UNKNOWN);
1354                 final int oldLevel = intent
1355                         .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
1356                 if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
1357                         + " oldLevel=" + oldLevel);
1358                 if (stream != STREAM_UNKNOWN) {
1359                     changed |= onVolumeChangedW(stream, 0, false);
1360                 }
1361             } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
1362                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
1363                         STREAM_UNKNOWN);
1364                 final int devices = intent
1365                         .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
1366                 final int oldDevices = intent
1367                         .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
1368                 if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
1369                         + stream + " devices=" + devices + " oldDevices=" + oldDevices);
1370                 if (stream != STREAM_UNKNOWN) {
1371                     changed |= checkRoutedToBluetoothW(stream);
1372                     changed |= onVolumeChangedW(stream, 0, false);
1373                 }
1374             } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
1375                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
1376                         STREAM_UNKNOWN);
1377                 final boolean muted = intent
1378                         .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
1379                 if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
1380                         + " muted=" + muted);
1381                 if (stream != STREAM_UNKNOWN) {
1382                     changed = updateStreamMuteW(stream, muted);
1383                 }
1384             } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
1385                 if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
1386                 changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
1387             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
1388                 if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED");
1389                 mCallbacks.onConfigurationChanged();
1390             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
1391                 if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF");
1392                 mCallbacks.onScreenOff();
1393             } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
1394                 if (D.BUG) Log.d(TAG, "onReceive ACTION_CLOSE_SYSTEM_DIALOGS");
1395                 dismiss();
1396             }
1397             if (changed) {
1398                 mCallbacks.onStateChanged(mState);
1399             }
1400         }
1401     }
1402 
1403     protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
1404         private final HashMap<SessionId, Integer> mRemoteStreams = new HashMap<>();
1405 
1406         private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
1407 
1408         @Override
onRemoteUpdate( SessionId token, String name, MediaSessions.VolumeInfo volumeInfo)1409         public void onRemoteUpdate(
1410                     SessionId token, String name, MediaSessions.VolumeInfo volumeInfo) {
1411                 addStream(token, "onRemoteUpdate");
1412 
1413                 int stream = 0;
1414                 synchronized (mRemoteStreams) {
1415                     stream = mRemoteStreams.get(token);
1416                 }
1417                 Slog.d(TAG,
1418                         "onRemoteUpdate: stream: "
1419                                 + stream + " volume: " + volumeInfo.getCurrentVolume());
1420                 boolean changed = mState.states.indexOfKey(stream) < 0;
1421                 final StreamState ss = streamStateW(stream);
1422                 ss.dynamic = true;
1423                 ss.levelMin = 0;
1424                 ss.levelMax = volumeInfo.getMaxVolume();
1425                 if (ss.level != volumeInfo.getCurrentVolume()) {
1426                     ss.level = volumeInfo.getCurrentVolume();
1427                     changed = true;
1428                 }
1429                 if (!Objects.equals(ss.remoteLabel, name)) {
1430                     ss.name = -1;
1431                     ss.remoteLabel = name;
1432                     changed = true;
1433                 }
1434                 if (changed) {
1435                     Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + " of " + ss.levelMax);
1436                     mCallbacks.onStateChanged(mState);
1437                 }
1438         }
1439 
1440         @Override
1441         public void onRemoteVolumeChanged(SessionId sessionId, int flags) {
1442                 addStream(sessionId, "onRemoteVolumeChanged");
1443                 int stream = 0;
1444                 synchronized (mRemoteStreams) {
1445                     stream = mRemoteStreams.get(sessionId);
1446                 }
1447                 final boolean showUI = shouldShowUI(flags);
1448                 Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
1449                 boolean changed = updateActiveStreamW(stream);
1450                 if (showUI) {
1451                     changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
1452                 }
1453                 if (changed) {
1454                     Slog.d(TAG, "onRemoteChanged: updatingState");
1455                     mCallbacks.onStateChanged(mState);
1456                 }
1457                 if (showUI) {
1458                     onShowRequestedW(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
1459                 }
1460         }
1461 
1462         @Override
1463         public void onRemoteRemoved(SessionId token) {
1464             int stream;
1465             synchronized (mRemoteStreams) {
1466                 if (!mRemoteStreams.containsKey(token)) {
1467                     Log.d(
1468                             TAG,
1469                             "onRemoteRemoved: stream doesn't exist, "
1470                                     + "aborting remote removed for token:"
1471                                     + token.toString());
1472                     return;
1473                 }
1474                 stream = mRemoteStreams.get(token);
1475             }
1476             mState.states.remove(stream);
1477             if (mState.activeStream == stream) {
1478                 updateActiveStreamW(-1);
1479             }
1480             mCallbacks.onStateChanged(mState);
1481         }
1482 
1483         public void setStreamVolume(int stream, int level) {
1484             final SessionId token = findToken(stream);
1485             if (token == null) {
1486                 Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
1487                 return;
1488             }
1489             mMediaSessions.setVolume(token, level);
1490         }
1491 
1492         private SessionId findToken(int stream) {
1493             synchronized (mRemoteStreams) {
1494                 for (Map.Entry<SessionId, Integer> entry : mRemoteStreams.entrySet()) {
1495                     if (entry.getValue().equals(stream)) {
1496                         return entry.getKey();
1497                     }
1498                 }
1499             }
1500             return null;
1501         }
1502 
1503         private void addStream(SessionId token, String triggeringMethod) {
1504             synchronized (mRemoteStreams) {
1505                 if (!mRemoteStreams.containsKey(token)) {
1506                     mRemoteStreams.put(token, mNextStream);
1507                     Log.d(TAG, triggeringMethod + ": added stream " + mNextStream
1508                             + " from token + " + token.toString());
1509                     mNextStream++;
1510                 }
1511             }
1512         }
1513     }
1514 
1515     public interface UserActivityListener {
1516         void onUserActivity();
1517     }
1518 }
1519