diff --git a/packages/SystemUI/res/values/lineage_config.xml b/packages/SystemUI/res/values/lineage_config.xml index 79c2865567a74..ce3999cd0d48a 100644 --- a/packages/SystemUI/res/values/lineage_config.xml +++ b/packages/SystemUI/res/values/lineage_config.xml @@ -70,4 +70,9 @@ Possible values: 3, 3.1, 3.2, 4, 4.1, 4.2 --> 4.2 + + + @*android:bool/config_enableBurnInProtection + + 60 diff --git a/packages/SystemUI/res/values/lineage_dimens.xml b/packages/SystemUI/res/values/lineage_dimens.xml index ca6e7db4c5b18..c75933439b6cc 100644 --- a/packages/SystemUI/res/values/lineage_dimens.xml +++ b/packages/SystemUI/res/values/lineage_dimens.xml @@ -24,4 +24,9 @@ 48dp + + + 4dp + + 4dp diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt index 5c6478ed08952..e0dcd87382dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt @@ -63,8 +63,8 @@ fun getBurnInScale(): Float { * @param amplitude maximum value of the function * @return a value between 0 and amplitude */ -private fun zigzag(x: Float, amplitude: Float, period: Float): Float { +fun zigzag(x: Float, amplitude: Float, period: Float): Float { val xprime = x % period / (period / 2) val interpolationAmount = if (xprime <= 1) xprime else 2 - xprime return MathUtils.lerp(0f, amplitude, interpolationAmount) -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 3b86d302ac0ad..bf23768341433 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -72,6 +72,7 @@ import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; +import com.android.systemui.navigationbar.gestural.NavigationHandle; import com.android.systemui.recents.Recents; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; @@ -83,6 +84,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarTransitionsController; +import com.android.systemui.statusbar.policy.Offset; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -186,6 +188,9 @@ public class NavigationBarView extends FrameLayout { private boolean mShowCursorKeys; private boolean mImeVisible; + @Nullable + private ViewGroup mNavigationBarContents = null; + private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; private boolean mHomeAppearing; @@ -889,12 +894,31 @@ public void setAccessibilityButtonState(final boolean visible, final boolean lon mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible); } + public void offsetNavBar(Offset offset) { + if (isGesturalMode(mNavBarMode)) { + final NavigationHandle handle = (NavigationHandle) getHomeHandle().getCurrentView(); + if (handle != null) { + handle.setTranslationY(offset.getY()); + handle.invalidate(); + } + return; + } + if (mNavigationBarContents == null) { + return; + } + mNavigationBarContents.setTranslationX(offset.getX()); + mNavigationBarContents.setTranslationY(offset.getY()); + invalidate(); + } + @Override public void onFinishInflate() { super.onFinishInflate(); mNavigationInflaterView = findViewById(R.id.navigation_inflater); mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); + mNavigationBarContents = (ViewGroup) findViewById(R.id.nav_buttons); + updateOrientationViews(); reloadNavIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 18e95781a32c2..58153cac928d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -218,6 +218,7 @@ import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.statusbar.policy.BurnInProtectionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -598,6 +599,8 @@ public int getId() { private final SceneContainerFlags mSceneContainerFlags; + private final BurnInProtectionController mBurnInProtectionController; + /** * Public constructor for CentralSurfaces. * @@ -709,7 +712,8 @@ public CentralSurfacesImpl( UserTracker userTracker, Provider fingerprintManager, ActivityStarter activityStarter, - SceneContainerFlags sceneContainerFlags + SceneContainerFlags sceneContainerFlags, + BurnInProtectionController burnInProtectionController ) { mContext = context; mNotificationsController = notificationsController; @@ -805,6 +809,7 @@ public CentralSurfacesImpl( mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; mSceneContainerFlags = sceneContainerFlags; + mBurnInProtectionController = burnInProtectionController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1228,6 +1233,7 @@ protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { mShadeSurface.updateExpansionAndVisibility(); setBouncerShowingForStatusBarComponents(mBouncerShowing); checkBarModes(); + mBurnInProtectionController.setPhoneStatusBarView(mStatusBarView); }); mStatusBarInitializer.initializeStatusBar(); @@ -1498,6 +1504,7 @@ protected void setUpDisableFlags(int state1, int state2) { // Try to remove this. protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result); + mBurnInProtectionController.setNavigationBarView(getNavigationBarView()); } /** @@ -2503,6 +2510,8 @@ public void onFinishedGoingToSleep() { updateNotificationPanelTouchState(); getNotificationShadeWindowViewController().cancelCurrentTouch(); + mBurnInProtectionController.stopShiftTimer(); + if (mLaunchCameraOnFinishedGoingToSleep) { mLaunchCameraOnFinishedGoingToSleep = false; @@ -2668,6 +2677,7 @@ public void onFinishedWakingUp() { } } updateScrimController(); + mBurnInProtectionController.startShiftTimer(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index b275b3f35fa4e..ac6ed5847cc80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; @@ -56,6 +57,7 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.Offset; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; @@ -84,6 +86,8 @@ public class PhoneStatusBarView extends FrameLayout implements Callbacks { private Gefingerpoken mTouchEventHandler; private int mDensity; private float mFontScale; + @Nullable + private ViewGroup mStatusBarContents = null; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space @@ -156,6 +160,7 @@ public void onFinishInflate() { mBattery = findViewById(R.id.battery); mClockController = new ClockController(getContext(), this); mCutoutSpace = findViewById(R.id.cutout_space_view); + mStatusBarContents = (ViewGroup) findViewById(R.id.status_bar_contents); updateResources(); } @@ -221,6 +226,15 @@ public WindowInsets onApplyWindowInsets(WindowInsets insets) { return super.onApplyWindowInsets(insets); } + public void offsetStatusBar(Offset offset) { + if (mStatusBarContents == null) { + return; + } + mStatusBarContents.setTranslationX(offset.getX()); + mStatusBarContents.setTranslationY(offset.getY()); + invalidate(); + } + /** * @return boolean indicating if we need to update the cutout location / margins */ @@ -336,7 +350,7 @@ private void updatePaddings() { int statusBarPaddingStart = getResources().getDimensionPixelSize( R.dimen.status_bar_padding_start); - findViewById(R.id.status_bar_contents).setPaddingRelative( + mStatusBarContents.setPaddingRelative( statusBarPaddingStart, getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top), getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BurnInProtectionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BurnInProtectionController.kt new file mode 100644 index 0000000000000..ba498dd57b88b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BurnInProtectionController.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017-2018 Paranoid Android + * Copyright (C) 2022 FlamingoOS 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.policy + +import android.content.Context +import android.util.Log + +import com.android.internal.policy.SystemBarUtils + +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.doze.util.zigzag +import com.android.systemui.navigationbar.NavigationBarView +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.shared.system.QuickStepContract.isGesturalMode +import com.android.systemui.statusbar.phone.PhoneStatusBarView +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener + +import javax.inject.Inject + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + +private val TAG = BurnInProtectionController::class.simpleName + +@SysUISingleton +class BurnInProtectionController @Inject constructor( + private val context: Context, + configurationController: ConfigurationController, + navigationModeController: NavigationModeController, +) : NavigationModeController.ModeChangedListener, + ConfigurationListener { + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + private val shiftEnabled = context.resources.getBoolean( + R.bool.config_statusBarBurnInProtection) + + private val shiftInterval = context.resources.getInteger( + R.integer.config_statusBarBurnInProtectionShiftInterval) * 1000L + + private var navigationMode: Int = navigationModeController.addListener(this) + + private var navigationBarView: NavigationBarView? = null + private var phoneStatusBarView: PhoneStatusBarView? = null + + private var shiftJob: Job? = null + private var shiftCounter = 0 + + private var maxStatusBarOffsetX = 0 + private var maxStatusBarOffsetY = 0 + private var maxNavBarOffsetX = 0 + private var maxNavBarOffsetY = 0 + + private var statusBarOffset = Offset.Zero + private var navBarOffset = Offset.Zero + + init { + logD { + "shiftEnabled = $shiftEnabled, isGesturalMode = ${isGesturalMode()}" + } + configurationController.addCallback(this) + loadResources() + } + + private fun loadResources() { + with(context.resources) { + maxStatusBarOffsetX = minOf( + getDimensionPixelSize(R.dimen.status_bar_padding_start), + getDimensionPixelSize(R.dimen.status_bar_padding_end), + getDimensionPixelSize(R.dimen.status_bar_offset_max_x) + ) / 2 + maxStatusBarOffsetY = minOf( + SystemBarUtils.getStatusBarHeight(context) - + getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_default), + getDimensionPixelSize(R.dimen.status_bar_offset_max_y) + ) / 2 + } + calculateNavBarMaxOffset() + logD { + "maxStatusBarOffsetX = $maxStatusBarOffsetX, maxStatusBarOffsetY = $maxStatusBarOffsetY" + } + } + + private fun calculateNavBarMaxOffset() { + with(context.resources) { + maxNavBarOffsetX = if (isGesturalMode()) { + 0 + } else { + getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin) / 4 + } + maxNavBarOffsetY = if (isGesturalMode()) { + getDimensionPixelSize(R.dimen.navigation_handle_bottom) / 3 + } else { + val frameHeight = getDimensionPixelSize(R.dimen.navigation_bar_height) + val buttonHeight = getDimensionPixelSize(R.dimen.navigation_icon_size) + (frameHeight - buttonHeight) / 3 + } + } + logD { + "maxNavBarOffsetX = $maxNavBarOffsetX, maxNavBarOffsetY = $maxNavBarOffsetY" + } + } + + fun setNavigationBarView(navigationBarView: NavigationBarView?) { + this.navigationBarView = navigationBarView + } + + fun setPhoneStatusBarView(phoneStatusBarView: PhoneStatusBarView?) { + this.phoneStatusBarView = phoneStatusBarView + } + + fun startShiftTimer() { + if (!shiftEnabled || (shiftJob?.isActive == true)) return + shiftJob = coroutineScope.launch { + while (isActive) { + val sbOffset = Offset( + getBurnInOffset(maxStatusBarOffsetX), + getBurnInOffset(maxStatusBarOffsetY) + ) + val nbOffset = Offset( + getBurnInOffset(maxNavBarOffsetX), + getBurnInOffset(maxNavBarOffsetY) + ) + logD { + "new offsets: sbOffset = $sbOffset, nbOffset = $nbOffset" + } + updateViews(sbOffset, nbOffset) + delay(shiftInterval) + shiftCounter++ + } + } + logD { + "Started shift job" + } + } + + private fun getBurnInOffset(maxOffset: Int): Int { + val amplitude = maxOffset.toFloat() + val period = amplitude * 2 + val mult = if ((shiftCounter / period) % 2 == 0f) 1 else -1 + return mult * Math.round(zigzag(shiftCounter.toFloat(), amplitude, period)) + } + + private fun updateViews(sbOffset: Offset, nbOffset: Offset) { + if (sbOffset != statusBarOffset) { + logD { + "Translating statusbar" + } + phoneStatusBarView?.offsetStatusBar(sbOffset) + statusBarOffset = sbOffset + } + if (nbOffset != navBarOffset) { + logD { + "Translating navbar" + } + navigationBarView?.offsetNavBar(nbOffset) + navBarOffset = nbOffset + } + } + + fun stopShiftTimer() { + if (!shiftEnabled || (shiftJob?.isActive != true)) return + logD { + "Cancelling shift job" + } + coroutineScope.launch { + shiftJob?.cancelAndJoin() + updateViews(Offset.Zero, Offset.Zero) + logD { + "Cancelled shift job" + } + } + } + + override fun onNavigationModeChanged(mode: Int) { + if (navigationMode == mode) return + navigationMode = mode + logD { + "onNavigationModeChanged: isGesturalMode = ${isGesturalMode()}" + } + calculateNavBarMaxOffset() + } + + override fun onDensityOrFontScaleChanged() { + logD { + "onDensityOrFontScaleChanged" + } + loadResources() + } + + private fun isGesturalMode() = isGesturalMode(navigationMode) +} + +private inline fun logD(crossinline msg: () -> String) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, msg()) + } +} + +data class Offset( + val x: Int, + val y: Int +) { + companion object { + val Zero = Offset(0, 0) + } +}