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