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