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