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