Pulse initial checkin for Android 12 [1/2]
neobuddy89:
* This includes few of NPE fixes and improvements.
* Android 12 removed colored notifications in ff165814e10cf757981d1e0254bd88473dff126e,
so retire pulse color based on album art till we find other way.
----
ezio:
big squash here to handle Android 11 changes,
check external pulse tree to know who contributed
to this epic feature that Randall made back in the days
(yeah, we're getting old) :D
some mentions: neobuddy89, Alex Cruz, LorDClockaN, varund7726
ps: now when the the new qs media player notification is enabled,
a different call to media processor is done by the mediadatamanager
in packages/SystemUI/src/com/android/systemui/media/, that's why the
need of MediaDataManager.Listener in NotificationMediaManager.
reminder for myself: we can acces properties of kotlin "data" classes
like MediaData.kt just calling a built-in getter (like getBackgroundColor
for the backgroundColor prop). For reference, it's also possible to call
straihgt mediaData.background color but only after adding the @JvmField
flag to the prop declaration
----
Change-Id: I0205f8ba6b8883012d1b809de9a33f6391c4cf0c
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
Signed-off-by: spezi77 <spezi7713@gmx.net>
Signed-off-by: Ayan Mukherjee <mukherjeeayan725@gmail.com>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index db3091f..0901d4c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11498,6 +11498,96 @@
public static final String VOLUME_LINK_NOTIFICATION = "volume_link_notification";
/**
+ * Pulse navbar music visualizer
+ * @hide
+ */
+ public static final String NAVBAR_PULSE_ENABLED = "navbar_pulse_enabled";
+
+ /**
+ * Pulse lockscreen music visualizer
+ * @hide
+ */
+ public static final String LOCKSCREEN_PULSE_ENABLED = "lockscreen_pulse_enabled";
+
+ /**
+ * Pulse navbar music visualizer color type
+ * @hide
+ */
+ public static final String PULSE_COLOR_MODE = "pulse_color_mode";
+
+ /**
+ * Pulse music visualizer user defined color
+ * @hide
+ */
+ public static final String PULSE_COLOR_USER = "pulse_color_user";
+
+ /**
+ * Pulse lavalamp animation speed
+ * @hide
+ */
+ public static final String PULSE_LAVALAMP_SPEED = "pulse_lavalamp_speed";
+
+ /**
+ * Pulse renderer implementation
+ * @hide
+ */
+ public static final String PULSE_RENDER_STYLE = "pulse_render_style";
+
+ /**
+ * Custom Pulse Widths
+ * @hide
+ */
+ public static final String PULSE_CUSTOM_DIMEN = "pulse_custom_dimen";
+
+ /**
+ * Custom Spacing Between Pulse Bars
+ * @hide
+ */
+ public static final String PULSE_CUSTOM_DIV = "pulse_custom_div";
+
+ /**
+ * Custom Pulse Block Size
+ * @hide
+ */
+ public static final String PULSE_FILLED_BLOCK_SIZE = "pulse_filled_block_size";
+
+ /**
+ * Custom Spacing Between Pulse Blocks
+ * @hide
+ */
+ public static final String PULSE_EMPTY_BLOCK_SIZE = "pulse_empty_block_size";
+
+ /**
+ * Custom Pulse Sanity Levels
+ * @hide
+ */
+ public static final String PULSE_CUSTOM_FUDGE_FACTOR = "pulse_custom_fudge_factor";
+
+ /**
+ * Pulse Fudge Factor
+ * @hide
+ */
+ public static final String PULSE_SOLID_FUDGE_FACTOR = "pulse_solid_fudge_factor";
+
+ /**
+ * Pulse Solid units count
+ * @hide
+ */
+ public static final String PULSE_SOLID_UNITS_COUNT = "pulse_solid_units_count";
+
+ /**
+ * Pulse Solid units opacity
+ * @hide
+ */
+ public static final String PULSE_SOLID_UNITS_OPACITY = "pulse_solid_units_opacity";
+
+ /**
+ * Pulse uses FFT averaging
+ * @hide
+ */
+ public static final String PULSE_SMOOTHING_ENABLED = "pulse_smoothing_enabled";
+
+ /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f081285..8a9b0c2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -292,5 +292,6 @@
void toggleCameraFlash();
void toggleCameraFlashState(boolean enable);
void killForegroundApp();
-
+ void screenPinningStateChanged(boolean enabled);
+ void leftInLandscapeChanged(boolean isLeft);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a61c13d15..6d96176 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -176,4 +176,5 @@
void cancelPreloadRecentApps();
void toggleCameraFlashState(boolean enable);
void killForegroundApp();
+ void screenPinningStateChanged(boolean enabled);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 6893013..5dc59be 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -394,4 +394,11 @@
void registerModeDispatcher(IAudioModeDispatcher dispatcher);
oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
+
+ /**
+ * Internal api to protect Pulse
+ * @hide
+ */
+ void setVisualizerLocked(boolean doLock);
+ boolean isVisualizerLocked(String callingPackage);
}
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 3349277..bf0bca1 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -17,12 +17,19 @@
package android.media.audiofx;
import android.annotation.NonNull;
+import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
+import android.content.Context;
+import android.media.IAudioService;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -221,6 +228,28 @@
synchronized (mStateLock) {
mState = STATE_UNINITIALIZED;
+ // if audio service locks us out, stay uninitialized
+ // throw UnsupportedOperationException as caller is required
+ // to catch and handle it
+ boolean isLocked = false;
+ String packageName = "";
+ try {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ packageName = ActivityThread.currentOpPackageName();
+ if (packageName == null && android.os.Process.SYSTEM_UID == Binder.getCallingUid()) {
+ packageName = "android";
+ }
+ isLocked = audioService.isVisualizerLocked(packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "Error checking visualizer lock in AudioManager, disabling visualizer lock");
+ }
+ if (isLocked) {
+ throw (new UnsupportedOperationException(packageName
+ + " is locked out from Visualizer by Pulse"));
+ }
+
// native initialization
// TODO b/182469354: make consistent with AudioRecord
int result;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 673c2e7..0e3d9a7 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -295,6 +295,9 @@
<uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
+ <!-- Pulse -->
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bd32e4f..082b69b 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -64,6 +64,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <com.android.systemui.pulse.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" />
+
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 82f4470..e17328b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -119,6 +119,7 @@
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.PulseController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -368,6 +369,7 @@
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
@Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
@Inject Lazy<TaskHelper> mTaskHelper;
+ @Inject Lazy<PulseController> mPulseController;
@Inject
public Dependency() {
@@ -587,6 +589,7 @@
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
+ mProviders.put(PulseController.class, mPulseController::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 14a2bf2..085bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -65,6 +65,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.pulse.PulseControllerImpl;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
@@ -87,6 +88,7 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.PulseController;
import com.android.systemui.statusbar.policy.TaskHelper;
import com.android.systemui.theme.ThemeOverlayApplier;
import com.android.systemui.util.leak.LeakDetector;
@@ -391,4 +393,11 @@
public TaskHelper provideTaskHelper(Context context) {
return new TaskHelper(context);
}
+
+ /** */
+ @Provides
+ @SysUISingleton
+ public PulseController providePulseController(Context context, @Main Handler mainHandler, @Background Executor backgroundExecutor) {
+ return new PulseControllerImpl(context, mainHandler, backgroundExecutor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 1162080..e1b2ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -329,7 +329,9 @@
ButtonDispatcher buttonDispatcher = null;
boolean forceVisible = false;
- if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode);
+ boolean forceHideHomeHandle = isGesturalMode && mNavigationBarView.isHomeHandleForceHidden();
+ if (isGesturalMode) {
// Disallow home handle animations when in gestural
animate = false;
forceVisible = mAllowForceNavBarHandleOpaque && mForceNavBarHandleOpaque;
@@ -342,8 +344,12 @@
}
if (buttonDispatcher != null) {
buttonDispatcher.setVisibility(
- (forceVisible || alpha > 0) ? View.VISIBLE : View.INVISIBLE);
- buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate);
+ (!forceHideHomeHandle && (forceVisible || alpha > 0))
+ ? View.VISIBLE
+ : View.INVISIBLE);
+ buttonDispatcher.setAlpha(forceVisible ? 1f : alpha,
+ forceHideHomeHandle ? false : animate);
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
index 6c531d8..45cd822 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
@@ -26,12 +26,16 @@
import android.view.MotionEvent;
import android.widget.FrameLayout;
+import com.android.systemui.Dependency;
import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.statusbar.policy.PulseController;
public class NavigationBarFrame extends FrameLayout {
private DeadZone mDeadZone = null;
+ private boolean mAttached;
+
public NavigationBarFrame(@NonNull Context context) {
super(context);
}
@@ -58,4 +62,20 @@
}
return super.dispatchTouchEvent(event);
}
-}
\ No newline at end of file
+
+ @Override
+ public void onAttachedToWindow() {
+ mAttached = true;
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mAttached = false;
+ super.onDetachedFromWindow();
+ }
+
+ public boolean isAttached() {
+ return mAttached;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c4f1cf4..8421577 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -181,6 +181,8 @@
private RotationButtonController mRotationButtonController;
private NavigationBarOverlayController mNavBarOverlayController;
+ private boolean mHomeHandleForceHidden;
+
/**
* Helper that is responsible for showing the right toast when a disallowed activity operation
* occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
@@ -842,7 +844,7 @@
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
- getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
+ getHomeHandle().setVisibility(disableHomeHandle || mHomeHandleForceHidden ? View.INVISIBLE : View.VISIBLE);
if (getPowerButton() != null) {
getPowerButton().setVisibility(View.VISIBLE);
@@ -857,6 +859,18 @@
notifyActiveTouchRegions();
}
+ public void hideHomeHandle(boolean hide) {
+ mHomeHandleForceHidden = hide;
+ boolean disableRecent = isRecentsButtonDisabled() | !QuickStepContract.isLegacyMode(mNavBarMode);
+ boolean disableHomeHandle = disableRecent
+ && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
+ getHomeHandle().setVisibility(disableHomeHandle || hide ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ public boolean isHomeHandleForceHidden() {
+ return mHomeHandleForceHidden;
+ }
+
@VisibleForTesting
boolean isRecentsButtonDisabled() {
return mUseCarModeUi || !isOverviewEnabled()
@@ -1238,6 +1252,10 @@
return mIsVertical;
}
+ public NavigationBarFrame getNavbarFrame() {
+ return ((NavigationBarFrame) getRootView());
+ }
+
public void reorient() {
updateCurrentView();
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/ColorAnimator.java b/packages/SystemUI/src/com/android/systemui/pulse/ColorAnimator.java
new file mode 100644
index 0000000..4f28c0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/ColorAnimator.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2016-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * Produce a smooth HSV color wheel type animation much like a LavaLamp
+ *
+ */
+
+package com.android.systemui.pulse;
+
+import android.animation.ValueAnimator;
+import android.graphics.Color;
+
+public class ColorAnimator implements ValueAnimator.AnimatorUpdateListener {
+ public interface ColorAnimationListener {
+ public default void onColorChanged(ColorAnimator colorAnimator, int color) {}
+ public default void onStartAnimation(ColorAnimator colorAnimator, int firstColor) {}
+ public default void onStopAnimation(ColorAnimator colorAnimator, int lastColor) {}
+ }
+
+ public static final int ANIM_DEF_DURATION = 10 * 1000;
+ public static final String RED = "#ffff8080";
+ public static final String BLUE = "#ff8080ff";
+
+ protected final float[] from = new float[3], to = new float[3], hsv = new float[3];
+
+ protected ValueAnimator mColorAnim;
+ protected long mAnimTime = ANIM_DEF_DURATION;
+ protected int mFromColor = Color.parseColor(RED);
+ protected int mToColor = Color.parseColor(BLUE);
+ protected int mLastColor = Color.parseColor(RED);
+ protected boolean mIsRunning;
+
+ protected ColorAnimationListener mListener;
+
+ public ColorAnimator() {
+ this(ValueAnimator.ofFloat(0, 1));
+ }
+
+ public ColorAnimator(ValueAnimator valueAnimator) {
+ this(valueAnimator, ANIM_DEF_DURATION);
+ }
+
+ public ColorAnimator(ValueAnimator valueAnimator, long animDurationMillis) {
+ this(valueAnimator, animDurationMillis, Color.parseColor(RED), Color.parseColor(BLUE));
+ }
+
+ public ColorAnimator(ValueAnimator valueAnimator, long animDurationMillis, int fromColor,
+ int toColor) {
+ mAnimTime = animDurationMillis;
+ mFromColor = fromColor;
+ mToColor = toColor;
+ mColorAnim = valueAnimator;
+ mColorAnim.addUpdateListener(this);
+ }
+
+ public void start() {
+ stop();
+ Color.colorToHSV(mFromColor, from);
+ Color.colorToHSV(mToColor, to);
+ mColorAnim.setDuration(mAnimTime);
+ mColorAnim.setRepeatMode(ValueAnimator.REVERSE);
+ mColorAnim.setRepeatCount(ValueAnimator.INFINITE);
+ if (mListener != null) {
+ mListener.onStartAnimation(this, mFromColor);
+ }
+ mColorAnim.start();
+ mIsRunning = true;
+ }
+
+ public void stop() {
+ if (mColorAnim.isStarted()) {
+ mColorAnim.end();
+ mIsRunning = false;
+ if (mListener != null) {
+ mListener.onStopAnimation(this, mLastColor);
+ }
+ }
+ }
+
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ public void setAnimationTime(long millis) {
+ if (mAnimTime != millis) {
+ mAnimTime = millis;
+ if (mColorAnim.isRunning()) {
+ start();
+ }
+ }
+ }
+
+ public void setColorAnimatorListener(ColorAnimationListener listener) {
+ mListener = listener;
+ }
+
+ public void removeColorAnimatorListener(ColorAnimationListener listener) {
+ mListener = null;
+ }
+
+ public void onAnimationUpdate(ValueAnimator animation) {
+ // Transition along each axis of HSV (hue, saturation, value)
+ hsv[0] = from[0] + (to[0] - from[0]) * animation.getAnimatedFraction();
+ hsv[1] = from[1] + (to[1] - from[1]) * animation.getAnimatedFraction();
+ hsv[2] = from[2] + (to[2] - from[2]) * animation.getAnimatedFraction();
+
+ mLastColor = Color.HSVToColor(hsv);
+
+ if (mListener != null) {
+ mListener.onColorChanged(this, mLastColor);
+ }
+ }
+
+ public int getCurrentColor() {
+ return mLastColor;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/ColorController.java b/packages/SystemUI/src/com/android/systemui/pulse/ColorController.java
new file mode 100644
index 0000000..165a18b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/ColorController.java
@@ -0,0 +1,153 @@
+/**
+ * Copyright (C) 2020-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * Generalized renderer color state management and color event dispatch
+ */
+
+package com.android.systemui.pulse;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.TypedValue;
+
+import com.android.internal.util.ContrastColorUtil;
+
+public class ColorController extends ContentObserver
+ implements ColorAnimator.ColorAnimationListener,
+ ConfigurationController.ConfigurationListener {
+ public static final int COLOR_TYPE_ACCENT = 0;
+ public static final int COLOR_TYPE_USER = 1;
+ public static final int COLOR_TYPE_LAVALAMP = 2;
+ public static final int LAVA_LAMP_SPEED_DEF = 10000;
+
+ private Context mContext;
+ private Renderer mRenderer;
+ private ColorAnimator mLavaLamp;
+ private int mColorType;
+ private int mAccentColor;
+ private int mColor;
+
+ public ColorController(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ mLavaLamp = new ColorAnimator();
+ mLavaLamp.setColorAnimatorListener(this);
+ mAccentColor = getAccentColor();
+ updateSettings();
+ startListening();
+ Dependency.get(ConfigurationController.class).addCallback(this);
+ }
+
+ void setRenderer(Renderer renderer) {
+ mRenderer = renderer;
+ notifyRenderer();
+ }
+
+ void startListening() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_COLOR_MODE), false,
+ this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_COLOR_USER), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_LAVALAMP_SPEED), false, this,
+ UserHandle.USER_ALL);
+ }
+
+ void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ if (mColorType == COLOR_TYPE_LAVALAMP) {
+ stopLavaLamp();
+ }
+ mColorType = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_COLOR_MODE, COLOR_TYPE_LAVALAMP, UserHandle.USER_CURRENT);
+ mColor = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_COLOR_USER,
+ 0x92FFFFFF,
+ UserHandle.USER_CURRENT);
+ int lava_speed = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_LAVALAMP_SPEED, LAVA_LAMP_SPEED_DEF,
+ UserHandle.USER_CURRENT);
+ mLavaLamp.setAnimationTime(lava_speed);
+ notifyRenderer();
+ }
+
+ void notifyRenderer() {
+ if (mRenderer != null) {
+ if (mColorType == COLOR_TYPE_ACCENT) {
+ mRenderer.onUpdateColor(mAccentColor);
+ } else if (mColorType == COLOR_TYPE_USER) {
+ mRenderer.onUpdateColor(mColor);
+ } else if (mColorType == COLOR_TYPE_LAVALAMP && mRenderer.isValidStream()) {
+ startLavaLamp();
+ }
+ }
+ }
+
+ void startLavaLamp() {
+ if (mColorType == COLOR_TYPE_LAVALAMP) {
+ mLavaLamp.start();
+ }
+ }
+
+ void stopLavaLamp() {
+ mLavaLamp.stop();
+ }
+
+ int getAccentColor() {
+ final TypedValue value = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
+ return value.data;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateSettings();
+ }
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ final int lastAccent = mAccentColor;
+ final int currentAccent = getAccentColor();
+ if (lastAccent != currentAccent) {
+ mAccentColor = currentAccent;
+ if (mRenderer != null && mColorType == COLOR_TYPE_ACCENT) {
+ mRenderer.onUpdateColor(mAccentColor);
+ }
+ }
+ }
+
+ @Override
+ public void onColorChanged(ColorAnimator colorAnimator, int color) {
+ if (mRenderer != null) {
+ mRenderer.onUpdateColor(color);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/FFTAverage.java b/packages/SystemUI/src/com/android/systemui/pulse/FFTAverage.java
new file mode 100644
index 0000000..6dfb3b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/FFTAverage.java
@@ -0,0 +1,41 @@
+/**
+ *
+ * @author: Ritayan Chakraborty <ritayanout@gmail.com>
+ *
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.pulse;
+
+import java.util.ArrayDeque;
+
+class FFTAverage {
+ private static final int WINDOW_LENGTH = 2;
+ private static final float WINDOW_LENGTH_F = WINDOW_LENGTH;
+ private ArrayDeque<Float> window = new ArrayDeque<>(WINDOW_LENGTH);
+ private float average;
+
+ int average(int dB) {
+ // Waiting until window is full
+ if (window.size() >= WINDOW_LENGTH) {
+ Float first = window.pollFirst();
+ if (first != null)
+ average -= first;
+ }
+ float newValue = dB / WINDOW_LENGTH_F;
+ average += newValue;
+ window.offerLast(newValue);
+
+ return Math.round(average);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/FadingBlockRenderer.java b/packages/SystemUI/src/com/android/systemui/pulse/FadingBlockRenderer.java
new file mode 100644
index 0000000..238054f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/FadingBlockRenderer.java
@@ -0,0 +1,275 @@
+/**
+ * Copyright 2011, Felix Palmer
+ * Copyright (C) 2014 The TeamEos Project
+ * Copyright (C) 2016-2021 crDroid Android Project
+ *
+ * AOSP Navigation implementation by
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * Licensed under the MIT license:
+ * http://creativecommons.org/licenses/MIT/
+ *
+ * Old school FFT renderer adapted from
+ * @link https://github.com/felixpalmer/android-visualizer
+ *
+ */
+
+package com.android.systemui.pulse;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Bitmap.Config;
+import android.graphics.PorterDuff.Mode;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.TypedValue;
+
+public class FadingBlockRenderer extends Renderer {
+ //private static final int DEF_PAINT_ALPHA = (byte) 188;
+ private static final int DBFUZZ = 2;
+ private byte[] mFFTBytes;
+ private Paint mPaint;
+ private Paint mFadePaint;
+ private boolean mVertical;
+ private boolean mLeftInLandscape;
+ private FFTAverage[] mFFTAverage;
+ private float[] mFFTPoints;
+ private byte rfk, ifk;
+ private int dbValue;
+ private float magnitude;
+ private int mDivisions;
+ private int mDbFuzzFactor;
+ private int mPathEffect1;
+ private int mPathEffect2;
+ private Bitmap mCanvasBitmap;
+ private Canvas mCanvas;
+ private Matrix mMatrix;
+ private int mWidth;
+ private int mHeight;
+
+ private LegacySettingsObserver mObserver;
+ private boolean mSmoothingEnabled;
+
+ public FadingBlockRenderer(Context context, Handler handler, PulseView view,
+ PulseControllerImpl controller, ColorController colorController) {
+ super(context, handler, view, colorController);
+ mObserver = new LegacySettingsObserver(handler);
+ mPaint = new Paint();
+ mFadePaint = new Paint();
+ mFadePaint.setColor(Color.argb(200, 255, 255, 255));
+ mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
+ mMatrix = new Matrix();
+ mObserver.updateSettings();
+ mPaint.setAntiAlias(true);
+ onSizeChanged(0, 0, 0, 0);
+ }
+
+ @Override
+ public void onStreamAnalyzed(boolean isValid) {
+ mIsValidStream = isValid;
+ if (isValid) {
+ onSizeChanged(0, 0, 0, 0);
+ mColorController.startLavaLamp();
+ }
+ }
+
+ @Override
+ public void onFFTUpdate(byte[] bytes) {
+ int fudgeFactor = mKeyguardShowing ? mDbFuzzFactor * 4 : mDbFuzzFactor;
+ mFFTBytes = bytes;
+ if (mFFTBytes != null) {
+ if (mFFTPoints == null || mFFTPoints.length < mFFTBytes.length * 4) {
+ mFFTPoints = new float[mFFTBytes.length * 4];
+ }
+ int divisionLength = mFFTBytes.length / mDivisions;
+ if (mSmoothingEnabled) {
+ if (mFFTAverage == null || mFFTAverage.length != divisionLength) {
+ setupFFTAverage(divisionLength);
+ }
+ } else {
+ mFFTAverage = null;
+ }
+ for (int i = 0; i < divisionLength; i++) {
+ if (mVertical) {
+ mFFTPoints[i * 4 + 1] = i * 4 * mDivisions;
+ mFFTPoints[i * 4 + 3] = i * 4 * mDivisions;
+ } else {
+ mFFTPoints[i * 4] = i * 4 * mDivisions;
+ mFFTPoints[i * 4 + 2] = i * 4 * mDivisions;
+ }
+ rfk = mFFTBytes[mDivisions * i];
+ ifk = mFFTBytes[mDivisions * i + 1];
+ magnitude = (rfk * rfk + ifk * ifk);
+ dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;
+ if (mSmoothingEnabled) {
+ dbValue = mFFTAverage[i].average(dbValue);
+ }
+ if (mVertical) {
+ mFFTPoints[i * 4] = mLeftInLandscape ? 0 : mWidth;
+ mFFTPoints[i * 4 + 2] = mLeftInLandscape ? (dbValue * fudgeFactor + DBFUZZ)
+ : (mWidth - (dbValue * fudgeFactor + DBFUZZ));
+ } else {
+ mFFTPoints[i * 4 + 1] = mHeight;
+ mFFTPoints[i * 4 + 3] = mHeight - (dbValue * fudgeFactor + DBFUZZ);
+ }
+ }
+ }
+ if (mCanvas != null) {
+ mCanvas.drawLines(mFFTPoints, mPaint);
+ mCanvas.drawPaint(mFadePaint);
+ }
+ postInvalidate();
+ }
+
+ private void setupFFTAverage(int size) {
+ mFFTAverage = new FFTAverage[size];
+ for (int i = 0; i < size; i++) {
+ mFFTAverage[i] = new FFTAverage();
+ }
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (mView.getWidth() > 0 && mView.getHeight() > 0) {
+ mWidth = mView.getWidth();
+ mHeight = mView.getHeight();
+ mVertical = mKeyguardShowing ? mHeight < mWidth : mHeight > mWidth;
+ mCanvasBitmap = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888);
+ mCanvas = new Canvas(mCanvasBitmap);
+ }
+ }
+
+ @Override
+ public void setLeftInLandscape(boolean leftInLandscape) {
+ if (mLeftInLandscape != leftInLandscape) {
+ mLeftInLandscape = leftInLandscape;
+ onSizeChanged(0, 0, 0, 0);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mColorController.stopLavaLamp();
+ mCanvasBitmap = null;
+ }
+
+ @Override
+ public void onVisualizerLinkChanged(boolean linked) {
+ if (!linked) {
+ mColorController.stopLavaLamp();
+ }
+ }
+
+ @Override
+ public void onUpdateColor(int color) {
+ mPaint.setColor(color);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(mCanvasBitmap, mMatrix, null);
+ }
+
+ /*private int applyPaintAlphaToColor(int color) {
+ int opaqueColor = Color.rgb(Color.red(color),
+ Color.green(color), Color.blue(color));
+ return (DEF_PAINT_ALPHA << 24) | (opaqueColor & 0x00ffffff);
+ }*/
+
+ private class LegacySettingsObserver extends ContentObserver {
+ public LegacySettingsObserver(Handler handler) {
+ super(handler);
+ register();
+ }
+
+ void register() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_CUSTOM_DIMEN), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_CUSTOM_DIV), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_FILLED_BLOCK_SIZE), false,
+ this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_EMPTY_BLOCK_SIZE), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_CUSTOM_FUDGE_FACTOR), false,
+ this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_SMOOTHING_ENABLED), false,
+ this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateSettings();
+ }
+
+ public void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ final Resources res = mContext.getResources();
+
+ int emptyBlock = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_EMPTY_BLOCK_SIZE, 1,
+ UserHandle.USER_CURRENT);
+ int customDimen = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_CUSTOM_DIMEN, 14,
+ UserHandle.USER_CURRENT);
+ int numDivision = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_CUSTOM_DIV, 16,
+ UserHandle.USER_CURRENT);
+ int fudgeFactor = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_CUSTOM_FUDGE_FACTOR, 5,
+ UserHandle.USER_CURRENT);
+ int filledBlock = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_FILLED_BLOCK_SIZE, 4,
+ UserHandle.USER_CURRENT);
+
+ mPathEffect1 = getLimitedDimenValue(filledBlock, 4, 8, res);
+ mPathEffect2 = getLimitedDimenValue(emptyBlock, 0, 4, res);
+ mPaint.setPathEffect(null);
+ mPaint.setPathEffect(new android.graphics.DashPathEffect(new float[] {
+ mPathEffect1,
+ mPathEffect2
+ }, 0));
+ mPaint.setStrokeWidth(getLimitedDimenValue(customDimen, 1, 30, res));
+ mDivisions = validateDivision(numDivision);
+ mDbFuzzFactor = Math.max(2, Math.min(6, fudgeFactor));
+
+ mSmoothingEnabled = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_SMOOTHING_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ }
+ }
+
+ private static int getLimitedDimenValue(int val, int min, int max, Resources res) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ Math.max(min, Math.min(max, val)), res.getDisplayMetrics());
+ }
+
+ private static int validateDivision(int val) {
+ // if a bad value was passed from settings (not divisible by 2)
+ // reset to default value of 16. Validate range.
+ if (val % 2 != 0) {
+ val = 16;
+ }
+ return Math.max(2, Math.min(44, val));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/PulseControllerImpl.java b/packages/SystemUI/src/com/android/systemui/pulse/PulseControllerImpl.java
new file mode 100644
index 0000000..f37e1ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/PulseControllerImpl.java
@@ -0,0 +1,561 @@
+/**
+ * Copyright (C) 2014 The TeamEos Project
+ * Copyright (C) 2016-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * Control class for Pulse media fuctions and visualizer state management
+ * Basic logic flow inspired by Roman Birg aka romanbb in his Equalizer
+ * tile produced for Cyanogenmod
+ *
+ */
+
+package com.android.systemui.pulse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.Animation;
+import android.widget.FrameLayout;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.navigationbar.NavigationBarFrame;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.PulseController;
+import com.android.systemui.statusbar.policy.PulseController.PulseStateListener;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+@Singleton
+public class PulseControllerImpl
+ implements PulseController, CommandQueue.Callbacks,
+ ConfigurationController.ConfigurationListener {
+
+ public static final boolean DEBUG = false;
+
+ private static final String TAG = PulseControllerImpl.class.getSimpleName();
+ private static final int RENDER_STYLE_LEGACY = 0;
+ private static final int RENDER_STYLE_CM = 1;
+
+ private Context mContext;
+ private Handler mHandler;
+ private AudioManager mAudioManager;
+ private Renderer mRenderer;
+ private VisualizerStreamHandler mStreamHandler;
+ private ColorController mColorController;
+ private final List<PulseStateListener> mStateListeners = new ArrayList<>();
+ private SettingsObserver mSettingsObserver;
+ private PulseView mPulseView;
+ private int mPulseStyle;
+ private StatusBar mStatusbar;
+ private final PowerManager mPowerManager;
+
+ // Pulse state
+ private boolean mLinked;
+ private boolean mPowerSaveModeEnabled;
+ private boolean mScreenOn = true; // MUST initialize as true
+ private boolean mMusicStreamMuted;
+ private boolean mLeftInLandscape;
+ private boolean mScreenPinningEnabled;
+ private boolean mIsMediaPlaying;
+ private boolean mAttached;
+
+ private boolean mNavPulseEnabled;
+ private boolean mLsPulseEnabled;
+ private boolean mKeyguardShowing;
+ private boolean mDozing;
+ private boolean mKeyguardGoingAway;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mScreenOn = false;
+ doLinkage();
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mScreenOn = true;
+ doLinkage();
+ } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
+ mPowerSaveModeEnabled = mPowerManager.isPowerSaveMode();
+ doLinkage();
+ } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())
+ || (AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction()))) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_MUSIC) {
+ boolean muted = isMusicMuted(streamType);
+ if (mMusicStreamMuted != muted) {
+ mMusicStreamMuted = muted;
+ doLinkage();
+ }
+ }
+ }
+ }
+ };
+
+ private final VisualizerStreamHandler.Listener mStreamListener = new VisualizerStreamHandler.Listener() {
+ @Override
+ public void onStreamAnalyzed(boolean isValid) {
+ if (mRenderer != null) {
+ mRenderer.onStreamAnalyzed(isValid);
+ }
+ if (isValid) {
+ notifyStateListeners(true);
+ turnOnPulse();
+ } else {
+ doSilentUnlinkVisualizer();
+ }
+ }
+
+ @Override
+ public void onFFTUpdate(byte[] bytes) {
+ if (mRenderer != null) {
+ mRenderer.onFFTUpdate(bytes);
+ }
+ }
+
+ @Override
+ public void onWaveFormUpdate(byte[] bytes) {
+ if (mRenderer != null) {
+ mRenderer.onWaveFormUpdate(bytes);
+ }
+ }
+ };
+
+ private class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void register() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.NAVBAR_PULSE_ENABLED), false, this,
+ UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_PULSE_ENABLED), false, this,
+ UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_RENDER_STYLE), false, this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (uri.equals(Settings.Secure.getUriFor(Settings.Secure.NAVBAR_PULSE_ENABLED))
+ || uri.equals(Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_PULSE_ENABLED))) {
+ updateEnabled();
+ updatePulseVisibility();
+ } else if (uri.equals(Settings.Secure.getUriFor(Settings.Secure.PULSE_RENDER_STYLE))) {
+ updateRenderMode();
+ loadRenderer();
+ }
+ }
+
+ void updateSettings() {
+ updateEnabled();
+ updateRenderMode();
+ }
+
+ void updateEnabled() {
+ mNavPulseEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NAVBAR_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ mLsPulseEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCKSCREEN_PULSE_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
+ }
+
+ void updateRenderMode() {
+ mPulseStyle = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PULSE_RENDER_STYLE, RENDER_STYLE_CM, UserHandle.USER_CURRENT);
+ }
+ };
+
+ public void notifyKeyguardGoingAway() {
+ if (mLsPulseEnabled) {
+ mKeyguardGoingAway = true;
+ updatePulseVisibility();
+ mKeyguardGoingAway = false;
+ }
+ }
+
+ private void updatePulseVisibility() {
+ if (mStatusbar == null) return;
+
+ NavigationBarFrame nv = mStatusbar.getNavigationBarView() != null ?
+ mStatusbar.getNavigationBarView().getNavbarFrame() : null;
+ VisualizerView vv = mStatusbar.getLsVisualizer();
+ boolean allowLsPulse = vv != null && vv.isAttached()
+ && mLsPulseEnabled && mKeyguardShowing && !mDozing;
+ boolean allowNavPulse = nv!= null && nv.isAttached()
+ && mNavPulseEnabled && !mKeyguardShowing;
+
+ if (mKeyguardGoingAway) {
+ detachPulseFrom(vv, allowNavPulse/*keep linked*/);
+ return;
+ }
+ if (!allowNavPulse) {
+ detachPulseFrom(nv, allowLsPulse/*keep linked*/);
+ }
+ if (!allowLsPulse) {
+ detachPulseFrom(vv, allowNavPulse/*keep linked*/);
+ }
+
+ if (allowLsPulse) {
+ attachPulseTo(vv);
+ } else if (allowNavPulse) {
+ attachPulseTo(nv);
+ }
+ }
+
+ public void setDozing(boolean dozing) {
+ if (mDozing != dozing) {
+ mDozing = dozing;
+ updatePulseVisibility();
+ }
+ }
+
+ public void setKeyguardShowing(boolean showing) {
+ if (showing != mKeyguardShowing) {
+ mKeyguardShowing = showing;
+ if (mRenderer != null) {
+ mRenderer.setKeyguardShowing(showing);
+ }
+ updatePulseVisibility();
+ }
+ }
+
+ @Inject
+ public PulseControllerImpl(Context context, @Main Handler mainHandler, @Background Executor backgroundExecutor) {
+ mContext = context;
+ mStatusbar = Dependency.get(StatusBar.class);
+ mHandler = mainHandler;
+ mSettingsObserver = new SettingsObserver(mainHandler);
+ mSettingsObserver.updateSettings();
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mMusicStreamMuted = isMusicMuted(AudioManager.STREAM_MUSIC);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mPowerSaveModeEnabled = mPowerManager.isPowerSaveMode();
+ mSettingsObserver.register();
+ mStreamHandler = new VisualizerStreamHandler(mContext, this, mStreamListener, backgroundExecutor);
+ mPulseView = new PulseView(context, this);
+ mColorController = new ColorController(mContext, mHandler);
+ loadRenderer();
+ Dependency.get(CommandQueue.class).addCallback(this);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ }
+
+ @Override
+ public void attachPulseTo(FrameLayout parent) {
+ if (parent == null) return;
+ View v = parent.findViewWithTag(PulseView.TAG);
+ if (v == null) {
+ parent.addView(mPulseView);
+ mAttached = true;
+ log("attachPulseTo() ");
+ doLinkage();
+ }
+ }
+
+ @Override
+ public void detachPulseFrom(FrameLayout parent, boolean keepLinked) {
+ if (parent == null) return;
+ View v = parent.findViewWithTag(PulseView.TAG);
+ if (v != null) {
+ parent.removeView(v);
+ mAttached = keepLinked;
+ log("detachPulseFrom() ");
+ doLinkage();
+ }
+ }
+
+ @Override
+ public void addCallback(PulseStateListener listener) {
+ mStateListeners.add(listener);
+ if (shouldDrawPulse()) {
+ listener.onPulseStateChanged(true);
+ }
+ }
+
+ @Override
+ public void removeCallback(PulseStateListener listener) {
+ mStateListeners.remove(listener);
+ }
+
+ private void notifyStateListeners(boolean isStarting) {
+ for (PulseStateListener listener : mStateListeners) {
+ if (listener != null) {
+ if (isStarting) {
+ listener.onPulseStateChanged(true);
+ } else {
+ listener.onPulseStateChanged(false);
+ }
+ }
+ }
+ }
+
+ private void loadRenderer() {
+ final boolean isRendering = shouldDrawPulse();
+ if (isRendering) {
+ mStreamHandler.pause();
+ }
+ if (mRenderer != null) {
+ mRenderer.destroy();
+ mRenderer = null;
+ }
+ mRenderer = getRenderer();
+ mColorController.setRenderer(mRenderer);
+ mRenderer.setLeftInLandscape(mLeftInLandscape);
+ if (isRendering) {
+ mRenderer.onStreamAnalyzed(true);
+ mStreamHandler.resume();
+ }
+ }
+
+ @Override
+ public void screenPinningStateChanged(boolean enabled) {
+ mScreenPinningEnabled = enabled;
+ doLinkage();
+ }
+
+ @Override
+ public void leftInLandscapeChanged(boolean isLeft) {
+ if (mLeftInLandscape != isLeft) {
+ mLeftInLandscape = isLeft;
+ if (mRenderer != null) {
+ mRenderer.setLeftInLandscape(isLeft);
+ }
+ }
+ }
+
+ /**
+ * Current rendering state: There is a visualizer link and the fft stream is validated
+ *
+ * @return true if bar elements should be hidden, false if not
+ */
+ public boolean shouldDrawPulse() {
+ return mLinked && mStreamHandler.isValidStream() && mRenderer != null;
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (shouldDrawPulse()) {
+ mRenderer.draw(canvas);
+ }
+ }
+
+ private void turnOnPulse() {
+ if (shouldDrawPulse()) {
+ mStreamHandler.resume(); // let bytes hit visualizer
+ }
+ }
+
+ void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (mRenderer != null) {
+ mRenderer.onSizeChanged(w, h, oldw, oldh);
+ }
+ }
+
+ private Renderer getRenderer() {
+ switch (mPulseStyle) {
+ case RENDER_STYLE_LEGACY:
+ return new FadingBlockRenderer(mContext, mHandler, mPulseView, this, mColorController);
+ case RENDER_STYLE_CM:
+ return new SolidLineRenderer(mContext, mHandler, mPulseView, this, mColorController);
+ default:
+ return new FadingBlockRenderer(mContext, mHandler, mPulseView, this, mColorController);
+ }
+ }
+
+ private boolean isMusicMuted(int streamType) {
+ return streamType == AudioManager.STREAM_MUSIC &&
+ (mAudioManager.isStreamMute(streamType) ||
+ mAudioManager.getStreamVolume(streamType) == 0);
+ }
+
+ private static void setVisualizerLocked(boolean doLock) {
+ try {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ audioService.setVisualizerLocked(doLock);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting visualizer lock");
+ }
+ }
+
+ /**
+ * if any of these conditions are met, we unlink regardless of any other states
+ *
+ * @return true if unlink is required, false if unlinking is not mandatory
+ */
+ private boolean isUnlinkRequired() {
+ return !mScreenOn
+ || mPowerSaveModeEnabled
+ || mMusicStreamMuted
+ || mScreenPinningEnabled
+ || !mAttached;
+ }
+
+ /**
+ * All of these conditions must be met to allow a visualizer link
+ *
+ * @return true if all conditions are met to allow link, false if and conditions are not met
+ */
+ private boolean isAbleToLink() {
+ return mScreenOn
+ && mIsMediaPlaying
+ && !mPowerSaveModeEnabled
+ && !mMusicStreamMuted
+ && !mScreenPinningEnabled
+ && mAttached;
+ }
+
+ private void doUnlinkVisualizer() {
+ if (mStreamHandler != null) {
+ if (mLinked) {
+ mStreamHandler.unlink();
+ setVisualizerLocked(false);
+ mLinked = false;
+ if (mRenderer != null) {
+ mRenderer.onVisualizerLinkChanged(false);
+ }
+ mPulseView.postInvalidate();
+ notifyStateListeners(false);
+ }
+ }
+ }
+
+ /**
+ * Incoming event in which we need to
+ * toggle our link state. Use runnable to
+ * handle multiple events at same time.
+ */
+ private void doLinkage() {
+ if (isUnlinkRequired()) {
+ if (mLinked) {
+ // explicitly unlink
+ doUnlinkVisualizer();
+ }
+ } else {
+ if (isAbleToLink()) {
+ doLinkVisualizer();
+ } else if (mLinked) {
+ doUnlinkVisualizer();
+ }
+ }
+ }
+
+ /**
+ * Invalid media event not providing
+ * a data stream to visualizer. Unlink
+ * without calling into view. Like it
+ * never happened
+ */
+ private void doSilentUnlinkVisualizer() {
+ if (mStreamHandler != null) {
+ if (mLinked) {
+ mStreamHandler.unlink();
+ setVisualizerLocked(false);
+ mLinked = false;
+ }
+ }
+ }
+
+ /**
+ * Link to visualizer after conditions
+ * are confirmed
+ */
+ private void doLinkVisualizer() {
+ if (mStreamHandler != null) {
+ if (!mLinked) {
+ setVisualizerLocked(true);
+ mStreamHandler.link();
+ mLinked = true;
+ if (mRenderer != null) {
+ mRenderer.onVisualizerLinkChanged(true);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPrimaryMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
+ boolean isPlaying = state == PlaybackState.STATE_PLAYING;
+ if (mIsMediaPlaying != isPlaying) {
+ mIsMediaPlaying = isPlaying;
+ doLinkage();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return TAG + " " + getState();
+ }
+
+ private String getState() {
+ return "isAbleToLink() = " + isAbleToLink() + " "
+ + "shouldDrawPulse() = " + shouldDrawPulse() + " "
+ + "mScreenOn = " + mScreenOn + " "
+ + "mIsMediaPlaying = " + mIsMediaPlaying + " "
+ + "mLinked = " + mLinked + " "
+ + "mPowerSaveModeEnabled = " + mPowerSaveModeEnabled + " "
+ + "mMusicStreamMuted = " + mMusicStreamMuted + " "
+ + "mScreenPinningEnabled = " + mScreenPinningEnabled + " "
+ + "mAttached = " + mAttached + " "
+ + "mStreamHandler.isValidStream() = " + mStreamHandler.isValidStream() + " ";
+ }
+
+ private void log(String msg) {
+ if (DEBUG) {
+ Log.i(TAG, msg + " " + getState());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/PulseView.java b/packages/SystemUI/src/com/android/systemui/pulse/PulseView.java
new file mode 100644
index 0000000..e1826e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/PulseView.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2019 The AquariOS Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * This is were we draw Pulse. Attach to a ViewGroup and let the
+ * eye candy happen
+ *
+ */
+package com.android.systemui.pulse;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+
+public class PulseView extends View {
+ public static final String TAG = "PulseView";
+
+ private PulseControllerImpl mPulse;
+
+ public PulseView(Context context, PulseControllerImpl controller) {
+ super(context);
+ mPulse = controller;
+ setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setWillNotDraw(false);
+ setTag(TAG);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mPulse.onSizeChanged(w, h, oldw, oldh);
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ mPulse.onDraw(canvas);
+ super.onDraw(canvas);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/Renderer.java b/packages/SystemUI/src/com/android/systemui/pulse/Renderer.java
new file mode 100644
index 0000000..cded99c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/Renderer.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (C) 2016-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * Base class of things that render eye candy
+ *
+ */
+
+package com.android.systemui.pulse;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Handler;
+
+public abstract class Renderer implements VisualizerStreamHandler.Listener {
+ protected Context mContext;
+ protected Handler mHandler;
+ protected PulseView mView;
+ protected ColorController mColorController;
+ protected boolean mIsValidStream;
+
+ private static final long ANIM_FPS_MAX = 40;
+ private static final long ANIM_FPS_TO_MILLIS = 1000 / ANIM_FPS_MAX;
+ private long mCurrentTime;
+ private long mRenderCounter;
+ private long mCurrentCounter;
+
+ protected boolean mKeyguardShowing;
+
+ public Renderer(Context context, Handler handler, PulseView view, ColorController colorController) {
+ mContext = context;
+ mHandler = handler;
+ mView = view;
+ mColorController = colorController;
+ mRenderCounter = System.currentTimeMillis();
+ }
+
+ protected final void postInvalidate() {
+ mCurrentTime = System.currentTimeMillis();
+ mCurrentCounter = mCurrentTime - mRenderCounter;
+ if (mCurrentCounter >= ANIM_FPS_TO_MILLIS) {
+ mRenderCounter = mCurrentTime;
+ mView.postInvalidate();
+ }
+ }
+
+ public abstract void draw(Canvas canvas);
+
+ @Override
+ public void onWaveFormUpdate(byte[] bytes) {}
+
+ @Override
+ public void onFFTUpdate(byte[] fft) {}
+
+ public void onVisualizerLinkChanged(boolean linked) {}
+
+ public void destroy() {}
+
+ public void setLeftInLandscape(boolean leftInLandscape) {}
+
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {}
+
+ public void onUpdateColor(int color) {}
+
+ public boolean isValidStream() { return mIsValidStream; }
+
+ public void setKeyguardShowing(boolean showing) {
+ mKeyguardShowing = showing;
+ onSizeChanged(0, 0, 0, 0);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/SolidLineRenderer.java b/packages/SystemUI/src/com/android/systemui/pulse/SolidLineRenderer.java
new file mode 100644
index 0000000..d3d50fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/SolidLineRenderer.java
@@ -0,0 +1,291 @@
+/**
+ * Copyright (C) 2016-2021 crDroid Android Project
+ * Copyright (C) 2015 The CyanogenMod Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * Contributions from The CyanogenMod 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.pulse;
+
+import android.animation.ValueAnimator;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.core.graphics.ColorUtils;
+
+public class SolidLineRenderer extends Renderer {
+ private Paint mPaint;
+ private int mUnitsOpacity = 200;
+ private int mColor = Color.WHITE;
+ private ValueAnimator[] mValueAnimators;
+ private FFTAverage[] mFFTAverage;
+ private float[] mFFTPoints;
+
+ private byte rfk, ifk;
+ private int dbValue;
+ private float magnitude;
+ private int mDbFuzzFactor;
+ private boolean mVertical;
+ private boolean mLeftInLandscape;
+ private int mWidth, mHeight, mUnits;
+
+ private boolean mSmoothingEnabled;
+ private CMRendererObserver mObserver;
+
+ public SolidLineRenderer(Context context, Handler handler, PulseView view,
+ PulseControllerImpl controller, ColorController colorController) {
+ super(context, handler, view, colorController);
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mDbFuzzFactor = 5;
+ mObserver = new CMRendererObserver(handler);
+ mObserver.updateSettings();
+ loadValueAnimators();
+ }
+
+ @Override
+ public void setLeftInLandscape(boolean leftInLandscape) {
+ if (mLeftInLandscape != leftInLandscape) {
+ mLeftInLandscape = leftInLandscape;
+ onSizeChanged(0, 0, 0, 0);
+ }
+ }
+
+ private void loadValueAnimators() {
+ if (mValueAnimators != null) {
+ stopAnimation(mValueAnimators.length);
+ }
+ mValueAnimators = new ValueAnimator[mUnits];
+ final boolean isVertical = mVertical;
+ for (int i = 0; i < mUnits; i++) {
+ final int j;
+ if (isVertical) {
+ j = i * 4;
+ } else {
+ 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 stopAnimation(int index) {
+ if (mValueAnimators == null) return;
+ for (int i = 0; i < index; i++) {
+ // prevent onAnimationUpdate existing listeners (by stopping them) to call
+ // a wrong mFFTPoints index after mUnits gets updated by the user
+ mValueAnimators[i].removeAllUpdateListeners();
+ mValueAnimators[i].cancel();
+ }
+ }
+
+ private void setPortraitPoints() {
+ float units = Float.valueOf(mUnits);
+ float barUnit = mWidth / units;
+ float barWidth = barUnit * 8f / 9f;
+ barUnit = barWidth + (barUnit - barWidth) * units / (units - 1);
+ mPaint.setStrokeWidth(barWidth);
+ for (int i = 0; i < mUnits; i++) {
+ mFFTPoints[i * 4] = mFFTPoints[i * 4 + 2] = i * barUnit + (barWidth / 2);
+ mFFTPoints[i * 4 + 1] = mHeight;
+ mFFTPoints[i * 4 + 3] = mHeight;
+ }
+ }
+
+ private void setVerticalPoints() {
+ float units = Float.valueOf(mUnits);
+ float barUnit = mHeight / units;
+ float barHeight = barUnit * 8f / 9f;
+ barUnit = barHeight + (barUnit - barHeight) * units / (units - 1);
+ mPaint.setStrokeWidth(barHeight);
+ for (int i = 0; i < mUnits; i++) {
+ mFFTPoints[i * 4 + 1] = mFFTPoints[i * 4 + 3] = i * barUnit + (barHeight / 2);
+ mFFTPoints[i * 4] = mLeftInLandscape ? 0 : mWidth;
+ mFFTPoints[i * 4 + 2] = mLeftInLandscape ? 0 : mWidth;
+ }
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (mView.getWidth() > 0 && mView.getHeight() > 0) {
+ mWidth = mView.getWidth();
+ mHeight = mView.getHeight();
+ mVertical = mKeyguardShowing ? mHeight < mWidth : mHeight > mWidth;
+ loadValueAnimators();
+ if (mVertical) {
+ setVerticalPoints();
+ } else {
+ setPortraitPoints();
+ }
+ }
+ }
+
+ @Override
+ public void onStreamAnalyzed(boolean isValid) {
+ mIsValidStream = isValid;
+ if (isValid) {
+ onSizeChanged(0, 0, 0, 0);
+ mColorController.startLavaLamp();
+ }
+ }
+
+ @Override
+ public void onFFTUpdate(byte[] fft) {
+ int fudgeFactor = mKeyguardShowing ? mDbFuzzFactor * 4 : mDbFuzzFactor;
+ for (int i = 0; i < mUnits; i++) {
+ if (mValueAnimators[i] == null) continue;
+ 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;
+ if (mSmoothingEnabled) {
+ if (mFFTAverage == null) {
+ setupFFTAverage();
+ }
+ dbValue = mFFTAverage[i].average(dbValue);
+ }
+ if (mVertical) {
+ if (mLeftInLandscape) {
+ mValueAnimators[i].setFloatValues(mFFTPoints[i * 4],
+ dbValue * fudgeFactor);
+ } else {
+ mValueAnimators[i].setFloatValues(mFFTPoints[i * 4],
+ mFFTPoints[2] - (dbValue * fudgeFactor));
+ }
+ } else {
+ mValueAnimators[i].setFloatValues(mFFTPoints[i * 4 + 1],
+ mFFTPoints[3] - (dbValue * fudgeFactor));
+ }
+ mValueAnimators[i].start();
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawLines(mFFTPoints, mPaint);
+ }
+
+ @Override
+ public void destroy() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mColorController.stopLavaLamp();
+ }
+
+ @Override
+ public void onVisualizerLinkChanged(boolean linked) {
+ if (!linked) {
+ mColorController.stopLavaLamp();
+ }
+ }
+
+ @Override
+ public void onUpdateColor(int color) {
+ mColor = color;
+ mPaint.setColor(ColorUtils.setAlphaComponent(mColor, mUnitsOpacity));
+ }
+
+ private class CMRendererObserver extends ContentObserver {
+ public CMRendererObserver(Handler handler) {
+ super(handler);
+ register();
+ }
+
+ void register() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_SOLID_FUDGE_FACTOR), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_SOLID_UNITS_COUNT), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_SOLID_UNITS_OPACITY), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.PULSE_SMOOTHING_ENABLED), false,
+ this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateSettings();
+ }
+
+ public void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+
+ // putFloat, getFloat is better. catch it next time
+ mDbFuzzFactor = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_SOLID_FUDGE_FACTOR, 4,
+ UserHandle.USER_CURRENT);
+ mSmoothingEnabled = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_SMOOTHING_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+
+ int units = Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_SOLID_UNITS_COUNT, 32,
+ UserHandle.USER_CURRENT);
+ if (units != mUnits) {
+ stopAnimation(mUnits);
+ mUnits = units;
+ mFFTPoints = new float[mUnits * 4];
+ if (mSmoothingEnabled) {
+ setupFFTAverage();
+ }
+ onSizeChanged(0, 0, 0, 0);
+ }
+
+ if (mSmoothingEnabled) {
+ if (mFFTAverage == null) {
+ setupFFTAverage();
+ }
+ } else {
+ mFFTAverage = null;
+ }
+
+ mUnitsOpacity= Settings.Secure.getIntForUser(
+ resolver, Settings.Secure.PULSE_SOLID_UNITS_OPACITY, 200,
+ UserHandle.USER_CURRENT);
+
+ mPaint.setColor(ColorUtils.setAlphaComponent(mColor, mUnitsOpacity));
+ }
+ }
+
+ private void setupFFTAverage() {
+ mFFTAverage = new FFTAverage[mUnits];
+ for (int i = 0; i < mUnits; i++) {
+ mFFTAverage[i] = new FFTAverage();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/VisualizerStreamHandler.java b/packages/SystemUI/src/com/android/systemui/pulse/VisualizerStreamHandler.java
new file mode 100644
index 0000000..47f1ff6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/VisualizerStreamHandler.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (C) 2016-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.
+ *
+ * Control state of visualizer link, stream validation, and the flow
+ * of data to listener
+ *
+ */
+
+package com.android.systemui.pulse;
+
+import android.content.Context;
+import android.media.audiofx.Visualizer;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+public class VisualizerStreamHandler {
+ public interface Listener {
+ public void onStreamAnalyzed(boolean isValid);
+
+ public void onFFTUpdate(byte[] bytes);
+
+ public void onWaveFormUpdate(byte[] bytes);
+ }
+
+ protected static final String TAG = VisualizerStreamHandler.class.getSimpleName();
+ protected static final boolean ENABLE_WAVEFORM = false;
+
+ protected static final int MSG_STREAM_VALID = 55;
+ protected static final int MSG_STREAM_INVALID = 56;
+ // we have 6 seconds to get three consecutive valid frames
+ protected static final int VALIDATION_TIME_MILLIS = 6000;
+ protected static final int VALID_BYTES_THRESHOLD = 3;
+
+ protected Visualizer mVisualizer;
+
+ // manage stream validation
+ protected int mConsecutiveFrames;
+ protected boolean mIsValidated;
+ protected boolean mIsAnalyzed;
+ protected boolean mIsPrepared;
+ protected boolean mIsPaused;
+
+ protected Context mContext;
+ protected PulseControllerImpl mController;
+ protected Listener mListener;
+
+ private final Executor mUiBgExecutor;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_STREAM_VALID:
+ mIsAnalyzed = true;
+ mIsValidated = true;
+ mIsPrepared = false;
+ mListener.onStreamAnalyzed(true);
+ break;
+ case MSG_STREAM_INVALID:
+ mIsAnalyzed = true;
+ mIsValidated = false;
+ mIsPrepared = false;
+ mListener.onStreamAnalyzed(false);
+ break;
+ }
+ }
+ };
+
+ public VisualizerStreamHandler(Context context, PulseControllerImpl controller,
+ VisualizerStreamHandler.Listener listener, @Background Executor backgroundExecutor) {
+ mContext = context;
+ mController = controller;
+ mListener = listener;
+ mUiBgExecutor = backgroundExecutor;
+ }
+
+ /**
+ * Links the visualizer to a player
+ */
+ public final void link() {
+ mUiBgExecutor.execute(() -> {
+ pause();
+ resetAnalyzer();
+
+ if (mVisualizer == null) {
+ try {
+ mVisualizer = new Visualizer(0);
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling visualizer!", e);
+ return;
+ }
+ mVisualizer.setEnabled(false);
+ mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
+
+ Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() {
+ @Override
+ public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
+ int samplingRate) {
+ if (ENABLE_WAVEFORM) {
+ analyze(bytes);
+ if (isValidStream() && !mIsPaused) {
+ mListener.onWaveFormUpdate(bytes);
+ }
+ }
+ }
+
+ @Override
+ public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
+ int samplingRate) {
+ analyze(bytes);
+ if (isValidStream() && !mIsPaused) {
+ mListener.onFFTUpdate(bytes);
+ }
+ }
+ };
+
+ mVisualizer.setDataCaptureListener(captureListener,
+ (int) (Visualizer.getMaxCaptureRate() * 0.75), ENABLE_WAVEFORM, true);
+
+ }
+ mVisualizer.setEnabled(true);
+ });
+ }
+
+ public final void unlink() {
+ if (mVisualizer != null) {
+ pause();
+ mVisualizer.setEnabled(false);
+ mVisualizer.release();
+ mVisualizer = null;
+ resetAnalyzer();
+ }
+ }
+
+ public boolean isValidStream() {
+ return mIsAnalyzed && mIsValidated;
+ }
+
+ public void resetAnalyzer() {
+ mIsAnalyzed = false;
+ mIsValidated = false;
+ mIsPrepared = false;
+ mConsecutiveFrames = 0;
+ }
+
+ public void pause() {
+ mIsPaused = true;
+ }
+
+ public void resume() {
+ mIsPaused = false;
+ }
+
+ private void analyze(byte[] data) {
+ if (mIsAnalyzed) {
+ return;
+ }
+
+ if (!mIsPrepared) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_STREAM_INVALID),
+ VALIDATION_TIME_MILLIS);
+ mIsPrepared = true;
+ }
+
+ if (isDataEmpty(data)) {
+ mConsecutiveFrames = 0;
+ } else {
+ mConsecutiveFrames++;
+ }
+
+ if (mConsecutiveFrames == VALID_BYTES_THRESHOLD) {
+ mIsPaused = true;
+ mHandler.removeMessages(MSG_STREAM_INVALID);
+ mHandler.sendEmptyMessage(MSG_STREAM_VALID);
+ }
+ }
+
+ private boolean isDataEmpty(byte[] data) {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pulse/VisualizerView.java b/packages/SystemUI/src/com/android/systemui/pulse/VisualizerView.java
new file mode 100644
index 0000000..28aff4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pulse/VisualizerView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source 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.pulse;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class VisualizerView extends FrameLayout {
+
+ private boolean mAttached;
+
+ public VisualizerView(@NonNull Context context) {
+ super(context);
+ }
+
+ public VisualizerView(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public VisualizerView(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mAttached = true;
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mAttached = false;
+ super.onDetachedFromWindow();
+ }
+
+ public boolean isAttached() {
+ return mAttached;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a72e0c0..ebe11f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -151,6 +151,8 @@
private static final int MSG_TOGGLE_CAMERA_FLASH_STATE = 62 << MSG_SHIFT;
private static final int MSG_KILL_FOREGROUND_APP = 63 << MSG_SHIFT;
private static final int MSG_TOGGLE_SETTINGS_PANEL = 64 << MSG_SHIFT;
+ private static final int MSG_SCREEN_PINNING_STATE_CHANGED = 65 << MSG_SHIFT;
+ private static final int MSG_LEFT_IN_LANDSCAPE_STATE_CHANGED = 66 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -409,6 +411,8 @@
default void toggleCameraFlash() { }
default void toggleCameraFlashState(boolean enable) { }
default void killForegroundApp() { }
+ default void screenPinningStateChanged(boolean enabled) { }
+ default void leftInLandscapeChanged(boolean isLeft) { }
}
public CommandQueue(Context context) {
@@ -1144,6 +1148,24 @@
}
}
+ @Override
+ public void screenPinningStateChanged(boolean enabled) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_SCREEN_PINNING_STATE_CHANGED);
+ mHandler.obtainMessage(MSG_SCREEN_PINNING_STATE_CHANGED,
+ enabled ? 1 : 0, 0, null).sendToTarget();
+ }
+ }
+
+ @Override
+ public void leftInLandscapeChanged(boolean isLeft) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_LEFT_IN_LANDSCAPE_STATE_CHANGED);
+ mHandler.obtainMessage(MSG_LEFT_IN_LANDSCAPE_STATE_CHANGED,
+ isLeft ? 1 : 0, 0, null).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1537,6 +1559,16 @@
mCallbacks.get(i).killForegroundApp();
}
break;
+ case MSG_SCREEN_PINNING_STATE_CHANGED:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).screenPinningStateChanged(msg.arg1 != 0);
+ }
+ break;
+ case MSG_LEFT_IN_LANDSCAPE_STATE_CHANGED:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).leftInLandscapeChanged(msg.arg1 != 0);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index e4ae560..b8a4c72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -58,6 +58,7 @@
* Gets the inflated {@link NotificationShadeWindowView} from
* {@link R.layout#super_notification_shade}.
* Returns a cached instance, if it has already been inflated.
+ * This will inflate also Pulse VisualizerView
*/
public NotificationShadeWindowView getNotificationShadeWindowView() {
if (mNotificationShadeWindowView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a36cab6..4934e72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -200,6 +200,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.pulse.VisualizerView;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -265,6 +266,7 @@
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.PulseController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.TaskHelper;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
@@ -1053,6 +1055,9 @@
// Connect in to the status bar manager service
mCommandQueue.addCallback(this);
+ // this will initialize Pulse and begin listening for media events
+ mMediaManager.addCallback(Dependency.get(PulseController.class));
+
// Listen for demo mode changes
mDemoModeController.addCallback(this);
@@ -1429,6 +1434,8 @@
});
}
+ mVisualizerView = (VisualizerView) mNotificationShadeWindowView.findViewById(R.id.visualizerview);
+
mReportRejectedTouch = mNotificationShadeWindowView
.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
@@ -3979,6 +3986,7 @@
// bar.
mKeyguardStateController.notifyKeyguardGoingAway(true);
mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
+ Dependency.get(PulseController.class).notifyKeyguardGoingAway();
}
/**
@@ -4043,6 +4051,7 @@
mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
mVisualizerView.setDozing(mDozing);
+ Dependency.get(PulseController.class).setDozing(mDozing);
updateQsExpansionEnabled();
Trace.endSection();
}
@@ -4214,10 +4223,15 @@
updateScrimController();
mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mVisualizerView.setStatusBarState(newState);
+ Dependency.get(PulseController.class).setKeyguardShowing(mState == StatusBarState.KEYGUARD);
updateKeyguardState();
Trace.endSection();
}
+ public VisualizerView getLsVisualizer() {
+ return mVisualizerView;
+ }
+
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.useNewLockscreenAnimations()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PulseController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PulseController.java
new file mode 100644
index 0000000..5d760aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PulseController.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020-2021 crDroid Android Project
+ *
+ * @author: Randall Rushing <randall.rushing@gmail.com>
+ *
+ * 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.policy;
+
+import android.widget.FrameLayout;
+
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.pulse.VisualizerView;
+import com.android.systemui.statusbar.NotificationMediaManager;
+
+public interface PulseController extends NotificationMediaManager.MediaListener {
+ public interface PulseStateListener {
+ public void onPulseStateChanged(boolean isRunning);
+ }
+
+ public void attachPulseTo(FrameLayout parent);
+ public void detachPulseFrom(FrameLayout parent, boolean keepLinked);
+ public void addCallback(PulseStateListener listener);
+ public void removeCallback(PulseStateListener listener);
+ public void setDozing(boolean dozing);
+ public void notifyKeyguardGoingAway();
+ public void setKeyguardShowing(boolean showing);
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d41afa9..199a8ae 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -786,6 +786,16 @@
return "card=" + card + ";device=" + device + ";";
}
+ // only these packages are allowed to override Pulse visualizer lock
+ private static final String[] VISUALIZER_WHITELIST = new String[] {
+ "android",
+ "com.android.systemui",
+ "com.android.keyguard",
+ "com.google.android.googlequicksearchbox"
+ };
+
+ private boolean mVisualizerLocked;
+
public static final class Lifecycle extends SystemService {
private AudioService mService;
@@ -5891,6 +5901,27 @@
return (mMuteAffectedStreams & (1 << streamType)) != 0;
}
+ /** @hide */
+ @Override
+ public boolean isVisualizerLocked(String callingPackage) {
+ boolean isSystem = false;
+ for (int i = 0; i < VISUALIZER_WHITELIST.length; i++) {
+ if (TextUtils.equals(callingPackage, VISUALIZER_WHITELIST[i])) {
+ isSystem = true;
+ break;
+ }
+ }
+ return !isSystem && mVisualizerLocked;
+ }
+
+ /** @hide */
+ @Override
+ public void setVisualizerLocked(boolean doLock) {
+ if (mVisualizerLocked != doLock) {
+ mVisualizerLocked = doLock;
+ }
+ }
+
private void ensureValidDirection(int direction) {
switch (direction) {
case AudioManager.ADJUST_LOWER:
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index cc4e7d9..788ae4f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -174,4 +174,9 @@
* @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
*/
void setUdfpsHbmListener(IUdfpsHbmListener listener);
+
+ /**
+ * Window manager notifies SystemUI of navigation bar "left in landscape" changes
+ */
+ void leftInLandscapeChanged(boolean isLeft);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b5269f7..951b6467 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -615,6 +615,16 @@
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void leftInLandscapeChanged(boolean isLeft) {
+ if (mBar != null) {
+ try {
+ mBar.leftInLandscapeChanged(isLeft);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
@@ -1160,6 +1170,23 @@
}
}
+ /**
+ * Let systemui know screen pinning state change. This is independent of the
+ * showScreenPinningRequest() call as it does not reflect state
+ *
+ * @hide
+ */
+ @Override
+ public void screenPinningStateChanged(boolean enabled) {
+ enforceStatusBar();
+ if (mBar != null) {
+ try {
+ mBar.screenPinningStateChanged(enabled);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
private void setDisableFlags(int displayId, int flags, String cause) {
// also allows calls from window manager which is in this process.
enforceStatusBarService();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a12fbae..ee399de 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1551,6 +1551,15 @@
contentFrame.set(sTmpRect);
}
+ private void notifyLeftInLandscapeChanged(boolean isOnLeft) {
+ mHandler.post(() -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.leftInLandscapeChanged(isOnLeft);
+ }
+ });
+ }
+
private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
if (mNavigationBar == null) {
return NAV_BAR_INVALID;
@@ -1564,10 +1573,17 @@
final int rotation = displayFrames.mRotation;
final int displayHeight = displayFrames.mDisplayHeight;
final int displayWidth = displayFrames.mDisplayWidth;
+ final int lastNavbarPosition = mNavigationBarPosition;
final int navBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
getRotatedWindowBounds(displayFrames, mNavigationBar, navigationFrame);
+ if (lastNavbarPosition == NAV_BAR_LEFT && mNavigationBarPosition != NAV_BAR_LEFT) {
+ notifyLeftInLandscapeChanged(false);
+ } else if (lastNavbarPosition != NAV_BAR_LEFT && mNavigationBarPosition == NAV_BAR_LEFT) {
+ notifyLeftInLandscapeChanged(true);
+ }
+
final Rect cutoutSafeUnrestricted = sTmpRect;
cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 94a175c..027cf8f 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -554,6 +554,7 @@
getStatusBarService().showPinningEnterExitToast(false /* entering */);
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
+ getStatusBarService().screenPinningStateChanged(false);
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
@@ -675,6 +676,7 @@
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId);
}
+ getStatusBarService().screenPinningStateChanged(true);
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}