Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a98695b authored by Roman Birg's avatar Roman Birg
Browse files

SystemUI: visualizer state improvements



- extract out media controller logic into a separate class to use in
  both visualizer locations
- improve reliability of visualizer showing up as expected

Change-Id: Ica6f000fd28c28ab546452fffcceb8648652b90b
Signed-off-by: default avatarRoman Birg <roman@cyngn.com>
parent 52a01e5c
Loading
Loading
Loading
Loading
+29 −105
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.MediaMonitor;
import com.pheelicks.visualizer.AudioData;
import com.pheelicks.visualizer.FFTData;
import com.pheelicks.visualizer.VisualizerView;
@@ -50,37 +51,48 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class VisualizerTile extends QSTile<QSTile.State>
        implements MediaSessionManager.OnActiveSessionsChangedListener, KeyguardMonitor.Callback {
public class VisualizerTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback {

    private static final Intent AUDIO_EFFECTS =
            new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);

    private Map<MediaSession.Token, CallbackInfo> mCallbacks = new HashMap<>();
    private MediaSessionManager mMediaSessionManager;
    private KeyguardMonitor mKeyguardMonitor;
    private VisualizerView mVisualizer;
    private ImageView mStaticVisualizerIcon;
    private boolean mLinked;
    private boolean mIsAnythingPlaying;
    private boolean mListening;
    private boolean mPowerSaveModeEnabled;

    private MediaMonitor mMediaMonitor;

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(intent.getAction())) {
                mPowerSaveModeEnabled = intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE,
                        false);
                checkIfPlaying(null);
                if (mPowerSaveModeEnabled) {
                    AsyncTask.execute(mUnlinkVisualizer);
                } else {
                    mMediaMonitor.checkIfPlaying(null);
                }
            }
        }
    };

    public VisualizerTile(Host host) {
        super(host);
        mMediaSessionManager = (MediaSessionManager)
                mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);

        mMediaMonitor = new MediaMonitor(mContext) {
            @Override
            public void onPlayStateChanged(boolean playing) {
                doLinkage();

                mHandler.removeCallbacks(mRefreshStateRunnable);
                mHandler.postDelayed(mRefreshStateRunnable, 50);
            }
        };

        mKeyguardMonitor = host.getKeyguardMonitor();
        mKeyguardMonitor.addCallback(this);

@@ -88,23 +100,8 @@ public class VisualizerTile extends QSTile<QSTile.State>
                new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING));
        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mPowerSaveModeEnabled = pm.isPowerSaveMode();
        if (!mPowerSaveModeEnabled) {
            mIsAnythingPlaying = isAnythingPlayingColdCheck();
        }

        mMediaSessionManager.addOnActiveSessionsChangedListener(this, null);
    }

    private boolean isAnythingPlayingColdCheck() {
        List<MediaController> activeSessions = mMediaSessionManager.getActiveSessions(null);
        for (MediaController activeSession : activeSessions) {
            PlaybackState playbackState = activeSession.getPlaybackState();
            if (playbackState != null && playbackState.getState()
                    == PlaybackState.STATE_PLAYING) {
                return true;
            }
        }
        return false;
        mMediaMonitor.setListening(true);
    }

    @Override
@@ -191,53 +188,17 @@ public class VisualizerTile extends QSTile<QSTile.State>
        // refresh state is called by QSPanel right after calling into this.
    }

    @Override
    public void onActiveSessionsChanged(@Nullable List<MediaController> controllers) {
        if (controllers != null) {
            for (MediaController controller : controllers) {
                if (!mCallbacks.containsKey(controller.getSessionToken())) {
                    mCallbacks.put(controller.getSessionToken(), new CallbackInfo(controller));
                }
            }
        }
    }

    @Override
    protected void handleDestroy() {
        mMediaMonitor.setListening(false);
        mMediaMonitor = null;
        super.handleDestroy();
        mIsAnythingPlaying = false;
        doLinkage();
        mMediaSessionManager.removeOnActiveSessionsChangedListener(this);
        for (Map.Entry<MediaSession.Token, CallbackInfo> entry : mCallbacks.entrySet()) {
            entry.getValue().unregister();
        }
        mCallbacks.clear();

        mKeyguardMonitor.removeCallback(this);
        mContext.unregisterReceiver(mReceiver);
    }

    private void checkIfPlaying(PlaybackState newState) {
        boolean anythingPlaying = newState == null
                ? mIsAnythingPlaying
                : newState.getState() == PlaybackState.STATE_PLAYING;
        if (!mPowerSaveModeEnabled && !anythingPlaying) {
            for (Map.Entry<MediaSession.Token, CallbackInfo> entry : mCallbacks.entrySet()) {
                if (entry.getValue().isPlaying()) {
                    anythingPlaying = true;
                    break;
                }
            }
        }

        if (anythingPlaying != mIsAnythingPlaying) {
            mIsAnythingPlaying = anythingPlaying;
            doLinkage();

            mHandler.removeCallbacks(mRefreshStateRunnable);
            mHandler.postDelayed(mRefreshStateRunnable, 50);
        }
    }

    @Override
    public void onKeyguardChanged() {
        doLinkage();
@@ -252,7 +213,10 @@ public class VisualizerTile extends QSTile<QSTile.State>
            }
        } else {
            // no keyguard, relink if there's something playing
            if (mIsAnythingPlaying && !mLinked && mListening) {
            if (mMediaMonitor.isAnythingPlaying()
                    && !mLinked
                    && mListening
                    && !mPowerSaveModeEnabled) {
                AsyncTask.execute(mLinkVisualizer);
            } else if (mLinked) {
                AsyncTask.execute(mUnlinkVisualizer);
@@ -272,7 +236,7 @@ public class VisualizerTile extends QSTile<QSTile.State>
    private final Runnable mUpdateVisibilities = new Runnable() {
        @Override
        public void run() {
            boolean showVz = mIsAnythingPlaying && !mKeyguardMonitor.isShowing();
            boolean showVz = mMediaMonitor.isAnythingPlaying() && !mKeyguardMonitor.isShowing();
            mVisualizer.animate().cancel();
            mVisualizer.animate()
                    .setDuration(200)
@@ -309,46 +273,6 @@ public class VisualizerTile extends QSTile<QSTile.State>
        }
    };

    private class CallbackInfo {
        MediaController.Callback mCallback;
        MediaController mController;
        boolean mIsPlaying;

        public CallbackInfo(final MediaController controller) {
            this.mController = controller;
            mCallback = new MediaController.Callback() {
                @Override
                public void onSessionDestroyed() {
                    destroy();
                    checkIfPlaying(null);
                }

                @Override
                public void onPlaybackStateChanged(@NonNull PlaybackState state) {
                    mIsPlaying = state.getState() == PlaybackState.STATE_PLAYING;
                    checkIfPlaying(state);
                }
            };
            controller.registerCallback(mCallback);
        }

        public boolean isPlaying() {
            return mIsPlaying;
        }

        public void unregister() {
            mController.unregisterCallback(mCallback);
            mIsPlaying = false;
        }

        public void destroy() {
            unregister();
            mCallbacks.remove(mController.getSessionToken());
            mController = null;
            mCallback = null;
        }
    }

    private static class TileBarGraphRenderer extends Renderer {
        private int mDivisions;
        private Paint mPaint;
+104 −34
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.PowerManager;
@@ -41,6 +42,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
import com.android.systemui.cm.UserContentObserver;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.MediaMonitor;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.pheelicks.visualizer.AudioData;
import com.pheelicks.visualizer.FFTData;
import com.pheelicks.visualizer.VisualizerView;
@@ -54,6 +57,7 @@ public class BackDropView extends FrameLayout {
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private PhoneStatusBar mPhoneStatusBar;
    private NotificationStackScrollLayout mStack;

    // the length to animate the visualizer in and out
    private static final int VISUALIZER_ANIMATION_DURATION_IN = 300;
@@ -66,22 +70,44 @@ public class BackDropView extends FrameLayout {
    private boolean mVisualizerEnabled;
    private boolean mPowerSaveModeEnabled;
    private SettingsObserver mSettingsObserver;
    private boolean mTouching;
    private MediaMonitor mMediaMonitor;
    private Handler mHandler;

    public BackDropView(Context context) {
        super(context);
        init();
    }

    public BackDropView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr,
                        int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        mHandler = new Handler();
        mMediaMonitor = new MediaMonitor(mContext) {
            @Override
            public void onPlayStateChanged(boolean playing) {
                if (playing) {
                    requestVisualizer(true, 500);
                } else {
                    // user paused, hide visualizer to stop flash
                    requestVisualizer(false, 0);
                }
            }
        };
    }

    @Override
@@ -95,17 +121,15 @@ public class BackDropView extends FrameLayout {
        if (changedView == this && mOnVisibilityChangedRunnable != null) {
            mOnVisibilityChangedRunnable.run();
        }
        if (!isShown()) {
            requestVisualizer(false, 0);
        }
    }

    public void setOnVisibilityChangedRunnable(Runnable runnable) {
        mOnVisibilityChangedRunnable = runnable;
    }

    public void setService(PhoneStatusBar service) {
    public void setService(PhoneStatusBar service, NotificationStackScrollLayout notificationStack) {
        mPhoneStatusBar = service;
        mStack = notificationStack;
    }

    @Override
@@ -124,7 +148,6 @@ public class BackDropView extends FrameLayout {
        super.onDetachedFromWindow();
        mSettingsObserver.unobserve();
        mContext.unregisterReceiver(mReceiver);
        requestVisualizer(false, 0);
    }

    @Override
@@ -153,24 +176,49 @@ public class BackDropView extends FrameLayout {
        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
    }

    public void setTouching(boolean touching) {
        if (DEBUG) Log.d(TAG, "setTouching() called with " + "touching = [" + touching + "]");
        if (mTouching != touching) {
            mTouching = touching;
            if (mTouching) {
                // immediately hide visualizer
                requestVisualizer(false, 0);
            } else {
                // we want to avoid requesting the visualizer when something is paused right here
                mHandler.postDelayed(mResumeVisualizerIfPlayingRunnable, 500);
            }
        }
    }

    public void requestVisualizer(boolean show, int delay) {
        if (mVisualizer == null || !mVisualizerEnabled || mPowerSaveModeEnabled) {
            return;
        }
        removeCallbacks(mStartVisualizer);
        removeCallbacks(mStopVisualizer);
        if (DEBUG) Log.d(TAG, "requestVisualizer(show: " + show + ", delay: " + delay + ")");
        mHandler.removeCallbacks(mStartVisualizer);
        mHandler.removeCallbacks(mStopVisualizer);
        if (DEBUG) Log.v(TAG, "requestVisualizer(show: " + show + ", delay: " + delay + ")");
        if (show && mScreenOn
                && mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD
                && !mPhoneStatusBar.isKeyguardFadingAway()
                && !mPhoneStatusBar.isGoingToNotificationShade()
                && mPhoneStatusBar.getCurrentMediaNotificationKey() != null) {
                && !mPhoneStatusBar.isInLaunchTransition()
                && !mPhoneStatusBar.isQsExpanded()
                && mPhoneStatusBar.getCurrentMediaNotificationKey() != null
                && mStack.getActivatedChild() == null
                ) {
            if (DEBUG) Log.d(TAG, "--> starting visualizer");
            postDelayed(mStartVisualizer, delay);
        } else {
            mHandler.postDelayed(mStartVisualizer, delay);
        } else if (!show) {
            if (DEBUG) Log.d(TAG, "--> stopping visualizer");
            postDelayed(mStopVisualizer, delay);
            mHandler.postDelayed(mStopVisualizer, delay);
        }
    }

    private void haltVisualizer() {
        mHandler.removeCallbacks(mResumeVisualizerIfPlayingRunnable);
        mHandler.removeCallbacks(mStartVisualizer);
        mHandler.removeCallbacks(mStopVisualizer);
        mHandler.post(mStopVisualizer);
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -179,7 +227,11 @@ public class BackDropView extends FrameLayout {
            if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(intent.getAction())) {
                mPowerSaveModeEnabled = intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE,
                        false);
                requestVisualizer(true, 0);
                if (mPowerSaveModeEnabled) {
                    mHandler.post(mStopVisualizer);
                } else {
                    mHandler.post(mResumeVisualizerIfPlayingRunnable);
                }
            }
        }
    };
@@ -192,15 +244,7 @@ public class BackDropView extends FrameLayout {
            mVisualizer.animate()
                    .alpha(1f)
                    .setDuration(VISUALIZER_ANIMATION_DURATION_IN);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    if (mVisualizer != null && !mLinked) {
                        mVisualizer.link(0);
                        mLinked = true;
                    }
                }
            });
            AsyncTask.execute(mLinkVisualizerRunnable);
        }
    };

@@ -212,7 +256,21 @@ public class BackDropView extends FrameLayout {
            mVisualizer.animate()
                    .alpha(0f)
                    .setDuration(VISUALIZER_ANIMATION_DURATION_OUT);
            AsyncTask.execute(new Runnable() {
            AsyncTask.execute(mUninkVisualizerRunnable);
        }
    };

    private final Runnable mLinkVisualizerRunnable = new Runnable() {
        @Override
        public void run() {
            if (mVisualizer != null && !mLinked) {
                mVisualizer.link(0);
                mLinked = true;
            }
        }
    };

    private final Runnable mUninkVisualizerRunnable = new Runnable() {
        @Override
        public void run() {
            if (mVisualizer != null && mLinked) {
@@ -220,7 +278,14 @@ public class BackDropView extends FrameLayout {
                mLinked = false;
            }
        }
            });
    };

    private Runnable mResumeVisualizerIfPlayingRunnable = new Runnable() {
        @Override
        public void run() {
            if (mMediaMonitor.isAnythingPlaying()) {
                requestVisualizer(true, 250);
            }
        }
    };

@@ -233,19 +298,24 @@ public class BackDropView extends FrameLayout {
                @Override
                public void onScreenTurnedOn() {
                    mScreenOn = true;
                    requestVisualizer(true, 300);
                    mHandler.postDelayed(mResumeVisualizerIfPlayingRunnable, 200);
                }

                @Override
                public void onScreenTurnedOff(int why) {
                    mScreenOn = false;
                    requestVisualizer(false, 0);
                    haltVisualizer();
                }

                @Override
                public void onKeyguardVisibilityChanged(boolean showing) {
                    mMediaMonitor.setListening(showing);

                    if (!showing) {
                        requestVisualizer(false, 0);
                        haltVisualizer();
                    } else if (mScreenOn) {
                        // in case keyguard is toggled back on even though screen never went off
                        mHandler.postDelayed(mResumeVisualizerIfPlayingRunnable, 200);
                    }
                }
            };
+5 −1
Original line number Diff line number Diff line
@@ -1025,7 +1025,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        mExpandedContents = mStackScroller;

        mBackdrop = (BackDropView) mStatusBarWindowContent.findViewById(R.id.backdrop);
        mBackdrop.setService(this);
        mBackdrop.setService(this, mStackScroller);
        mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
        mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);

@@ -5160,6 +5160,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        mBackdrop.requestVisualizer(show, delay);
    }

    public void setVisualizerTouching(boolean touching) {
        mBackdrop.setTouching(touching);
    }

    public void wakeUpIfDozing(long time, MotionEvent event) {
        if (mDozing && mDozeScrimController.isPulsing()) {
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+9 −4
Original line number Diff line number Diff line
@@ -200,11 +200,11 @@ public class StatusBarWindowView extends FrameLayout {
        if (mService.getBarState() == StatusBarState.KEYGUARD) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mService.requestVisualizer(false, 0);
                    mService.setVisualizerTouching(true);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mService.requestVisualizer(true, 500);
                    mService.setVisualizerTouching(false);
                    break;
            }
        }
@@ -249,9 +249,14 @@ public class StatusBarWindowView extends FrameLayout {
            handled = super.onTouchEvent(ev);
        }
        final int action = ev.getAction();
        if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            if (mService.getBarState() == StatusBarState.KEYGUARD) {
                mService.setVisualizerTouching(false);
            }
            if (!handled) {
                mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
            }
        }
        return handled;
    }

+159 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar.policy;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.util.Log;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Helper class which does the bookkeeping on media sessions
 * and reports when the current play state changes via {@link #onPlayStateChanged(boolean)}
 */
public abstract class MediaMonitor implements MediaSessionManager.OnActiveSessionsChangedListener {

    private Map<MediaSession.Token, CallbackInfo> mCallbacks = new HashMap<>();
    private MediaSessionManager mMediaSessionManager;
    private boolean mIsAnythingPlaying;
    private boolean mListening;

    public MediaMonitor(Context context) {
        mMediaSessionManager = (MediaSessionManager)
                context.getSystemService(Context.MEDIA_SESSION_SERVICE);

        mIsAnythingPlaying = isAnythingPlayingColdCheck();
    }

    public abstract void onPlayStateChanged(boolean playing);

    public boolean isAnythingPlaying() {
        return mIsAnythingPlaying;
    }

    public void setListening(boolean listening) {
        if (mListening == listening) return;
        mListening = listening;
        if (mListening) {
            mMediaSessionManager.addOnActiveSessionsChangedListener(this, null);
            checkIfPlaying(null);
        } else {
            mMediaSessionManager.removeOnActiveSessionsChangedListener(this);
            cleanup();
        }
    }

    private void cleanup() {
        for (Map.Entry<MediaSession.Token, CallbackInfo> entry : mCallbacks.entrySet()) {
            entry.getValue().unregister();
        }
        mCallbacks.clear();
        mIsAnythingPlaying = false;
    }

    @Override
    public void onActiveSessionsChanged(@Nullable List<MediaController> controllers) {
        if (controllers != null) {
            for (MediaController controller : controllers) {
                if (!mCallbacks.containsKey(controller.getSessionToken())) {
                    mCallbacks.put(controller.getSessionToken(), new CallbackInfo(controller));
                }
            }
        }
    }

    public void checkIfPlaying(PlaybackState newState) {
        boolean anythingPlaying = newState == null
                ? isAnythingPlayingColdCheck()
                : isStateConsideredPlaying(newState);
        if (!anythingPlaying) {
            for (Map.Entry<MediaSession.Token, CallbackInfo> entry : mCallbacks.entrySet()) {
                if (entry.getValue().isPlaying()) {
                    anythingPlaying = true;
                    break;
                }
            }
        }

        if (anythingPlaying != mIsAnythingPlaying) {
            mIsAnythingPlaying = anythingPlaying;
            if (mListening) {
                onPlayStateChanged(mIsAnythingPlaying);
            }
        }
    }

    private boolean isStateConsideredPlaying(PlaybackState state) {
        switch (state.getState()) {
            case PlaybackState.STATE_PLAYING:
//            case PlaybackState.STATE_SKIPPING_TO_NEXT:
//            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
//            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
                return true;
            default:
                return false;
        }
    }

    private class CallbackInfo {
        MediaController.Callback mCallback;
        MediaController mController;
        boolean mIsPlaying;

        public CallbackInfo(final MediaController controller) {
            this.mController = controller;
            mCallback = new MediaController.Callback() {
                @Override
                public void onSessionDestroyed() {
                    destroy();
                    checkIfPlaying(null);
                }

                @Override
                public void onPlaybackStateChanged(@NonNull PlaybackState state) {
                    mIsPlaying = state.getState() == PlaybackState.STATE_PLAYING;
                    checkIfPlaying(state);
                }
            };
            controller.registerCallback(mCallback);

            mIsPlaying = controller.getPlaybackState() != null
                    && controller.getPlaybackState().getState() == PlaybackState.STATE_PLAYING;
            checkIfPlaying(controller.getPlaybackState());
        }

        public boolean isPlaying() {
            return mIsPlaying;
        }

        public void unregister() {
            mController.unregisterCallback(mCallback);
            mIsPlaying = false;
        }

        public void destroy() {
            unregister();
            mCallbacks.remove(mController.getSessionToken());
            mController = null;
            mCallback = null;
        }
    }

    public boolean isAnythingPlayingColdCheck() {
        List<MediaController> activeSessions = mMediaSessionManager.getActiveSessions(null);
        for (MediaController activeSession : activeSessions) {
            PlaybackState playbackState = activeSession.getPlaybackState();
            if (playbackState != null && playbackState.getState()
                    == PlaybackState.STATE_PLAYING) {
                return true;
            }
        }
        return false;
    }
}
Loading