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

Commit 2168ac68 authored by Utkarsh Gupta's avatar Utkarsh Gupta Committed by Łukasz Patron
Browse files

SystemUI: Add visualizer feature



This is a squash of the following commits:

Author: Utkarsh Gupta <utkarsh.eminem@gmail.com>
Date:   Tue Aug 18 12:27:39 2015 +0530

    SystemUI: use new visualizer from Eleven

    Change-Id: I4441440868cac71caa32c91a0875f00a630fe596
    Signed-off-by: default avatarUtkarsh Gupta <utkarsh.eminem@gmail.com>
    Signed-off-by: default avatarRoman Birg <roman@cyngn.com>

Author: Mikalacki Sava <mikalackis@gmail.com>
Date:   Thu Sep 3 23:10:35 2015 +0200

    SystemUI: fix NPE crash when Visualizer is not initialized

    Change-Id: Ic3774011acfae0312f1642d88c7f5bf2abc9f5a7

Author: Roman Birg <roman@cyngn.com>
Date:   Fri Sep 4 15:55:39 2015 -0700

    SystemUI: hide visualizer when keyguard is occluded

    Change-Id: Ia943fd829f7a379fee0f657bb08c34b8c6f36caa
    Signed-off-by: default avatarRoman Birg <roman@cyngn.com>

Author: Roman Birg <roman@cyngn.com>
Date:   Thu Jan 7 19:27:38 2016 +0100

    SystemUI: Make lockscreen visualizer battery friendly

    In case of offloaded playback, the device is allowed to sleep.
    However, if the lockscreen visualizer is enabled, the device
    hardly enters deep sleep, even if the screen is off. To prevent
    this, enable the visualizer only when the lockscreen is shown.

    Change-Id: I484270694b734c9b53e61a7c0ad74391b54fe8cd

Author: Roman Birg <roman@cyngn.com>
Date:   Thu Mar 17 09:18:56 2016 -0700

    SystemUI: more consistent visualizer logic

    - only call setVisible from screen on and screen off methods
    - eagerly unregister self to make sure we don't get added twice as a
      listener
    - Add some debugging under the DEBUG flag

    Change-Id: Iada13058f87d4c8d8c8b60f6eebf055652ff2c8d
    Signed-off-by: default avatarRoman Birg <roman@cyngn.com>

Author: Roman Birg <roman@cyngn.com>
Date:   Mon Apr 4 15:29:34 2016 -0700

    SystemUI: show visualizer in shade-locked view

    This brings back the behavior we had in 12.1 - visualizer would still be
    visible in the shade locked state.

    Also with the following improvements:
        * use less alpha
        * always attach/detach in a background thread (Async task)
        * use the statusbar state instead keyguard view state listener

    Change-Id: I6b8a57f9f0bba6ba0591de0cb1f94a0eb904cc1c
    Signed-off-by: default avatarRoman Birg <roman@cyngn.com>

Author: Dan Pasanen <invisiblek@cyanogenmod.org>
Date:   Wed May 18 20:27:47 2016 -0500

    Visualizer: Let visualizer fill the entire available screen

    * This will allow it to be more dynamic (landscape lock screen for instance)

    Change-Id: I2ffd6b0ca8832812faaa4cc0ec4cdd6071fbec8c

Author: Matthias Yzusqui <myzb.dev@gmail.com>
Date:   Wed Sep 21 15:25:00 2016 +0200

    SystemUI: fix navbar drawing glitch

    Change-Id: I58e255517d35675c043061f2edb398dab0d16ba8

Author: Zhao Wei Liew <zhaoweiliew@gmail.com>
Date:   Fri Oct 7 08:56:25 2016 +0800

    SystemUI: Use Tuner API for CM settings

    Get rid of all the excess code by implementing TunerService.Tunable
    and observing any changes made to the settings through it.

    Remove UserContentObserver as the Tuner API handles user switches.

    Also remove some unused imports that were introduced in earlier
    CM commits, but were never removed since.

    Change-Id: Iecafafabdaec82b3b3c72293bea865de48f0e90a

Author: Bruno Martins <bgcngm@gmail.com>
Date:   Sat Dec 2 15:10:28 2017 +0000
Edit:   Forward-port visualizer feature to O

Author: Bruno Martins <bgcngm@gmail.com>
Date:   Sat Aug 25 01:42:06 2018 +0100
Edit:   Forward-port visualizer feature to P

Author: Bruno Martins <bgcngm@gmail.com>
Date:   Sat Sep 21 14:56:13 2019 +0100
Edit:   Forward-port visualizer feature to Q

Author: Bruno Martins <bgcngm@gmail.com>
Date:   Thu Oct 1 10:26:13 2020 +0100
Edit:   Forward-port visualizer feature to R

Author: Bruno Martins <bgcngm@gmail.com>
Date:   Mon Dec 27 22:35:13 2021 +0100
Edit:   Forward-port visualizer feature to S

Change-Id: I556a233f70f53f08084fd2cca319e3fb05f47243
parent 8da38f93
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -27,6 +27,10 @@
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
    <uses-permission android:name="lineageos.permission.HARDWARE_ABSTRACTION_ACCESS" />

    <!-- Visualizer -->
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <!-- SystemUI Tuner -->
    <application>
        <activity-alias
+10 −0
Original line number Diff line number Diff line
@@ -59,6 +59,16 @@
        sysui:ignoreRightInset="true"
        />

    <com.android.systemui.statusbar.VisualizerView
        android:id="@+id/visualizerview"
        android:gravity="bottom"
        android:layout_gravity="bottom"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible"
        sysui:ignoreRightInset="true"
    />

    <com.android.systemui.statusbar.LightRevealScrim
            android:id="@+id/light_reveal_scrim"
            android:layout_width="match_parent"
+38 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -139,6 +140,7 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable
    private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
    private final MediaArtworkProcessor mMediaArtworkProcessor;
    private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
    private final WallpaperManager mWallpaperManager;

    protected NotificationPresenter mPresenter;
    private MediaController mMediaController;
@@ -163,6 +165,8 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable
                    clearCurrentMediaNotification();
                }
                findAndUpdateMediaNotifications();
                mStatusBarOptionalLazy.get().map(StatusBar::getVisualizerView).ifPresent(
                        v -> v.setOccluded(state.getState() == PlaybackState.STATE_PLAYING));
            }
        }

@@ -193,7 +197,8 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable
            FeatureFlags featureFlags,
            @Main DelayableExecutor mainExecutor,
            MediaDataManager mediaDataManager,
            DumpManager dumpManager) {
            DumpManager dumpManager,
            WallpaperManager wallpaperManager) {
        mContext = context;
        mMediaArtworkProcessor = mediaArtworkProcessor;
        mKeyguardBypassController = keyguardBypassController;
@@ -217,6 +222,8 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable

        dumpManager.registerDumpable(this);

        mWallpaperManager = wallpaperManager;

        final TunerService tunerService = Dependency.get(TunerService.class);
        tunerService.addTunable(this, LOCKSCREEN_MEDIA_METADATA);
    }
@@ -669,9 +676,10 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable
        }
        boolean hasMediaArtwork = artworkDrawable != null;
        boolean allowWhenShade = false;
        Bitmap lockWallpaper = null;
        // if no media artwork, show normal lockscreen wallpaper
        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
            Bitmap lockWallpaper =
            lockWallpaper =
                    mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
            if (lockWallpaper != null) {
                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
@@ -693,6 +701,34 @@ public class NotificationMediaManager implements Dumpable, TunerService.Tunable
            mScrimController.setHasBackdrop(hasArtwork);
        }

        if (mStatusBarStateController.getState() != StatusBarState.SHADE) {
            boolean isScreenFullyOff =
                    !mStatusBarOptionalLazy.get().map(StatusBar::isScreenFullyOff).orElse(false);
            if (!mKeyguardStateController.isKeyguardFadingAway() && isScreenFullyOff) {
                mStatusBarOptionalLazy.get().map(StatusBar::getVisualizerView).ifPresent(
                        v -> v.setPlaying(getMediaControllerPlaybackState(mMediaController)
                                == PlaybackState.STATE_PLAYING));
            }

            Bitmap bitmap;

            if (artworkDrawable instanceof BitmapDrawable) {
                // always use current backdrop to color eq
                bitmap = ((BitmapDrawable) artworkDrawable).getBitmap();
            } else if (lockWallpaper instanceof Bitmap) {
                // use lockscreen wallpaper in case user set one
                bitmap = lockWallpaper.getConfig() == Bitmap.Config.HARDWARE
                        ? lockWallpaper.copy(Bitmap.Config.ARGB_8888, false)
                        : lockWallpaper;
            } else {
                // use regular wallpaper
                bitmap = mWallpaperManager.getBitmap(false);
            }

            mStatusBarOptionalLazy.get().map(StatusBar::getVisualizerView).ifPresent(
                    v -> v.setBitmap(bitmap));
        }

        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
                && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
                &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
+352 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The CyanogenMod Project
 *               2017-2019 The LineageOS Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.audiofx.Visualizer;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Palette.Swatch;
import com.android.systemui.Dependency;
import com.android.systemui.tuner.TunerService;

import lineageos.providers.LineageSettings;

public class VisualizerView extends View
        implements Palette.PaletteAsyncListener, TunerService.Tunable {

    private static final String TAG = VisualizerView.class.getSimpleName();
    private static final boolean DEBUG = false;

    private static final String LOCKSCREEN_VISUALIZER_ENABLED =
            "lineagesecure:" + LineageSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED;

    private Paint mPaint;
    private Visualizer mVisualizer;
    private ObjectAnimator mVisualizerColorAnimator;

    private ValueAnimator[] mValueAnimators;
    private float[] mFFTPoints;

    private int mStatusBarState;
    private boolean mVisualizerEnabled;
    private boolean mVisible;
    private boolean mPlaying;
    private boolean mDisplaying; // the state we're animating to
    private boolean mDozing;
    private boolean mOccluded;

    private int mColor;
    private Bitmap mCurrentBitmap;

    private Visualizer.OnDataCaptureListener mVisualizerListener =
            new Visualizer.OnDataCaptureListener() {
        byte rfk, ifk;
        int dbValue;
        float magnitude;

        @Override
        public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
        }

        @Override
        public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
            for (int i = 0; i < 32; i++) {
                mValueAnimators[i].cancel();
                rfk = fft[i * 2 + 2];
                ifk = fft[i * 2 + 3];
                magnitude = rfk * rfk + ifk * ifk;
                dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;

                mValueAnimators[i].setFloatValues(mFFTPoints[i * 4 + 1],
                        mFFTPoints[3] - (dbValue * 16f));
                mValueAnimators[i].start();
            }
        }
    };

    private final Runnable mLinkVisualizer = new Runnable() {
        @Override
        public void run() {
            if (DEBUG) {
                Log.w(TAG, "+++ mLinkVisualizer run()");
            }

            try {
                mVisualizer = new Visualizer(0);
            } catch (Exception e) {
                Log.e(TAG, "error initializing visualizer", e);
                return;
            }

            mVisualizer.setEnabled(false);
            mVisualizer.setCaptureSize(66);
            mVisualizer.setDataCaptureListener(mVisualizerListener, Visualizer.getMaxCaptureRate(),
                    false, true);
            mVisualizer.setEnabled(true);

            if (DEBUG) {
                Log.w(TAG, "--- mLinkVisualizer run()");
            }
        }
    };

    private final Runnable mAsyncUnlinkVisualizer = new Runnable() {
        @Override
        public void run() {
            AsyncTask.execute(mUnlinkVisualizer);
        }
    };

    private final Runnable mUnlinkVisualizer = new Runnable() {
        @Override
        public void run() {
            if (DEBUG) {
                Log.w(TAG, "+++ mUnlinkVisualizer run(), mVisualizer: " + mVisualizer);
            }
            if (mVisualizer != null) {
                mVisualizer.setEnabled(false);
                mVisualizer.release();
                mVisualizer = null;
            }
            if (DEBUG) {
                Log.w(TAG, "--- mUnlinkVisualizer run()");
            }
        }
    };

    public VisualizerView(Context context) {
        this(context, null, 0);
    }

    public VisualizerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mColor = Color.TRANSPARENT;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(mColor);

        mFFTPoints = new float[128];
        mValueAnimators = new ValueAnimator[32];
        for (int i = 0; i < 32; i++) {
            final int j = i * 4 + 1;
            mValueAnimators[i] = new ValueAnimator();
            mValueAnimators[i].setDuration(128);
            mValueAnimators[i].addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mFFTPoints[j] = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
        }
    }

    private void updateViewVisibility() {
        final int curVis = getVisibility();
        final int newVis = mStatusBarState != StatusBarState.SHADE &&
                mVisualizerEnabled ? View.VISIBLE : View.GONE;
        if (curVis != newVis) {
            setVisibility(newVis);
            checkStateChanged();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Dependency.get(TunerService.class).addTunable(this, LOCKSCREEN_VISUALIZER_ENABLED);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Dependency.get(TunerService.class).removeTunable(this);
        mCurrentBitmap = null;
    }

    @Override
    public void onTuningChanged(String key, String newValue) {
        if (LOCKSCREEN_VISUALIZER_ENABLED.equals(key)) {
            mVisualizerEnabled = TunerService.parseIntegerSwitch(newValue, false);
            checkStateChanged();
            updateViewVisibility();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        float barUnit = w / 32f;
        float barWidth = barUnit * 8f / 9f;
        barUnit = barWidth + (barUnit - barWidth) * 32f / 31f;
        mPaint.setStrokeWidth(barWidth);

        for (int i = 0; i < 32; i++) {
            mFFTPoints[i * 4] = mFFTPoints[i * 4 + 2] = i * barUnit + (barWidth / 2);
            mFFTPoints[i * 4 + 1] = h;
            mFFTPoints[i * 4 + 3] = h;
        }
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mVisualizer != null) {
            canvas.drawLines(mFFTPoints, mPaint);
        }
    }

    @Override
    public void onGenerated(Palette palette) {
        Swatch dominantSwatch = palette.getDominantSwatch();
        if (dominantSwatch != null) {
            setColor(dominantSwatch.getInt());
        }
    }

    public void setDozing(boolean dozing) {
        if (mDozing != dozing) {
            if (DEBUG) {
                Log.i(TAG, "setDozing() called with dozing = [" + dozing + "]");
            }
            mDozing = dozing;
            checkStateChanged();
        }
    }

    public void setOccluded(boolean occluded) {
        if (mOccluded != occluded) {
            if (DEBUG) {
                Log.i(TAG, "setOccluded() called with occluded = [" + occluded + "]");
            }
            mOccluded = occluded;
            checkStateChanged();
        }
    }

    public void setPlaying(boolean playing) {
        if (mPlaying != playing) {
            if (DEBUG) {
                Log.i(TAG, "setPlaying() called with playing = [" + playing + "]");
            }
            mPlaying = playing;
            checkStateChanged();
        }
    }

    public void setVisible(boolean visible) {
        if (mVisible != visible) {
            if (DEBUG) {
                Log.i(TAG, "setVisible() called with visible = [" + visible + "]");
            }
            mVisible = visible;
            checkStateChanged();
        }
    }

    public void setStatusBarState(int statusBarState) {
        if (mStatusBarState != statusBarState) {
            mStatusBarState = statusBarState;
            updateViewVisibility();
        }
    }

    public void setBitmap(Bitmap bitmap) {
        if (mCurrentBitmap == bitmap) {
            return;
        }
        mCurrentBitmap = bitmap;
        if (bitmap != null) {
            Palette.from(bitmap, null).generate(this);
        } else {
            setColor(Color.TRANSPARENT);
        }
    }

    private void setColor(int color) {
        if (color == Color.TRANSPARENT) {
            color = Color.WHITE;
        }

        color = Color.argb(140, Color.red(color), Color.green(color), Color.blue(color));

        if (mColor != color) {
            mColor = color;

            if (mVisualizer != null) {
                if (mVisualizerColorAnimator != null) {
                    mVisualizerColorAnimator.cancel();
                }

                mVisualizerColorAnimator = ObjectAnimator.ofArgb(mPaint, "color",
                        mPaint.getColor(), mColor);
                mVisualizerColorAnimator.setStartDelay(600);
                mVisualizerColorAnimator.setDuration(1200);
                mVisualizerColorAnimator.start();
            } else {
                mPaint.setColor(mColor);
            }
        }
    }

    private void checkStateChanged() {
        if (getVisibility() == View.VISIBLE && mVisible && mVisualizerEnabled && mPlaying &&
                !mDozing && !mOccluded) {
            if (!mDisplaying) {
                mDisplaying = true;
                AsyncTask.execute(mLinkVisualizer);
                animate()
                        .alpha(1f)
                        .withEndAction(null)
                        .setDuration(800);
            }
        } else {
            if (mDisplaying) {
                mDisplaying = false;
                animate()
                        .alpha(0f)
                        .withEndAction(mAsyncUnlinkVisualizer)
                        .setDuration(mVisible ? 600 : 0);
            }
        }
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.dagger;

import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.os.Handler;
import android.service.dreams.IDreamManager;
@@ -139,7 +140,8 @@ public interface StatusBarDependenciesModule {
            FeatureFlags featureFlags,
            @Main DelayableExecutor mainExecutor,
            MediaDataManager mediaDataManager,
            DumpManager dumpManager) {
            DumpManager dumpManager,
            WallpaperManager wallpaperManager) {
        return new NotificationMediaManager(
                context,
                statusBarOptionalLazy,
@@ -152,7 +154,8 @@ public interface StatusBarDependenciesModule {
                featureFlags,
                mainExecutor,
                mediaDataManager,
                dumpManager);
                dumpManager,
                wallpaperManager);
    }

    /** */
Loading