Home | History | Annotate | Download | only in volume
      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 
    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 
    123     public AudioManager getAudioManager() {
    124         return mAudio;
    125     }
    126 
    127     public void dismiss() {
    128         mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
    129     }
    130 
    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 
    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 
    157     protected MediaSessions createMediaSessions(Context context, Looper looper,
    158             MediaSessions.Callbacks callbacks) {
    159         return new MediaSessions(context, looper, callbacks);
    160     }
    161 
    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 
    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 
    187     public void addCallback(Callbacks callback, Handler handler) {
    188         mCallbacks.add(callback, handler);
    189     }
    190 
    191     public void removeCallback(Callbacks callback) {
    192         mCallbacks.remove(callback);
    193     }
    194 
    195     public void getState() {
    196         if (mDestroyed) return;
    197         mWorker.sendEmptyMessage(W.GET_STATE);
    198     }
    199 
    200     public void notifyVisible(boolean visible) {
    201         if (mDestroyed) return;
    202         mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
    203     }
    204 
    205     public void userActivity() {
    206         if (mDestroyed) return;
    207         mWorker.removeMessages(W.USER_ACTIVITY);
    208         mWorker.sendEmptyMessage(W.USER_ACTIVITY);
    209     }
    210 
    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 
    216     public void setZenMode(int value) {
    217         if (mDestroyed) return;
    218         mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
    219     }
    220 
    221     public void setExitCondition(Condition condition) {
    222         if (mDestroyed) return;
    223         mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
    224     }
    225 
    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 
    231     public void setStreamVolume(int stream, int level) {
    232         if (mDestroyed) return;
    233         mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
    234     }
    235 
    236     public void setActiveStream(int stream) {
    237         if (mDestroyed) return;
    238         mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
    239     }
    240 
    241     public void vibrate() {
    242         if (mHasVibrator) {
    243             mVibrator.vibrate(VIBRATE_HINT_DURATION);
    244         }
    245     }
    246 
    247     public boolean hasVibrator() {
    248         return mHasVibrator;
    249     }
    250 
    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 
    261     protected void onUserActivityW() {
    262         // hook for subclasses
    263     }
    264 
    265     private void onShowSafetyWarningW(int flags) {
    266         mCallbacks.onShowSafetyWarning(flags);
    267     }
    268 
    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 
    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 
    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 
    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 
    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 
    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 
    444     private void onSetRingerModeW(int mode, boolean external) {
    445         if (external) {
    446             mAudio.setRingerMode(mode);
    447         } else {
    448             mAudio.setRingerModeInternal(mode);
    449         }
    450     }
    451 
    452     private void onSetStreamMuteW(int stream, boolean mute) {
    453         mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
    454                 : AudioManager.ADJUST_UNMUTE, 0);
    455     }
    456 
    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 
    466     private void onSetActiveStreamW(int stream) {
    467         boolean changed = updateActiveStreamW(stream);
    468         if (changed) {
    469             mCallbacks.onStateChanged(mState);
    470         }
    471     }
    472 
    473     private void onSetExitConditionW(Condition condition) {
    474         mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
    475     }
    476 
    477     private void onSetZenModeW(int mode) {
    478         if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
    479         mNoMan.setZenMode(mode, null, TAG);
    480     }
    481 
    482     private void onDismissRequestedW(int reason) {
    483         mCallbacks.onDismissRequested(reason);
    484     }
    485 
    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
    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
    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
    511         public void masterMuteChanged(int flags) throws RemoteException {
    512             if (D.BUG) Log.d(TAG, "masterMuteChanged");
    513         }
    514 
    515         @Override
    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
    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 
    548         W(Looper looper) {
    549             super(looper);
    550         }
    551 
    552         @Override
    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 
    576         public void add(Callbacks callback, Handler handler) {
    577             if (callback == null || handler == null) throw new IllegalArgumentException();
    578             mCallbackMap.put(callback, handler);
    579         }
    580 
    581         public void remove(Callbacks callback) {
    582             mCallbackMap.remove(callback);
    583         }
    584 
    585         @Override
    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
    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
    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
    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
    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
    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
    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
    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
    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 
    706         public SettingObserver(Handler handler) {
    707             super(handler);
    708         }
    709 
    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 
    717         public void destroy() {
    718             mContext.getContentResolver().unregisterContentObserver(this);
    719         }
    720 
    721         @Override
    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 
    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 
    760         public void destroy() {
    761             mContext.unregisterReceiver(this);
    762         }
    763 
    764         @Override
    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
    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 
    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 {
    992         void onShowRequested(int reason);
    993         void onDismissRequested(int reason);
    994         void onStateChanged(State state);
    995         void onLayoutDirectionChanged(int layoutDirection);
    996         void onConfigurationChanged();
    997         void onShowVibrateHint();
    998         void onShowSilentHint();
    999         void onScreenOff();
   1000         void onShowSafetyWarning(int flags);
   1001     }
   1002 }
   1003