This reverts commit d9afe890a3.
This commit is contained in:
1 parent
d04b3b1c8d
commit
eba9492be1
17 files changed
+5
-1092
No files matched your search
@@ -1,14 +1,5 @@
|
||||
package app.lawnchair.root;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
interface IRootHelper {
|
||||
oneway void goToSleep();
|
||||
|
||||
/**
|
||||
* Creates an effect layer owned by root and returns it under the "layer" key. SurfaceFlinger
|
||||
* never ignores the blur of layers created by privileged processes, while app layers lose
|
||||
* all their blur past 10 blur requests on screen.
|
||||
*/
|
||||
Bundle createGlassBlurLayer();
|
||||
}
|
||||
@@ -330,16 +330,6 @@
|
||||
<!-- Icon-related settings -->
|
||||
<string name="transparent_background_icons_label">Transparent icon backgrounds</string>
|
||||
|
||||
<string name="glass_blur_label">Glass blur</string>
|
||||
<string name="glass_blur_icons_label">Blur wallpaper behind icons</string>
|
||||
<string name="glass_blur_icons_description">Live frosted glass behind home screen icons. Works best with translucent icons. Requires window blurs to be enabled, and root access to blur more than 10 items at once.</string>
|
||||
<string name="glass_blur_radius_label">Blur radius</string>
|
||||
<string name="glass_blur_tint_label">White tint</string>
|
||||
<string name="glass_blur_folders_label">Folders</string>
|
||||
<string name="glass_blur_folders_description">Folder icons and open folders. Folder colors and opacities set how dark the glass is</string>
|
||||
<string name="glass_blur_widgets_label">Widgets</string>
|
||||
<string name="glass_blur_widgets_description">Visible through translucent widgets</string>
|
||||
|
||||
<string name="auto_adaptive_icons_label">Auto-adaptive icons</string>
|
||||
<string name="shadow_bg_icons_label">Show shadow behind icons</string>
|
||||
<string name="background_lightness_label">Background lightness</string>
|
||||
|
||||
@@ -32,7 +32,6 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import app.lawnchair.LawnchairApp.Companion.showQuickstepWarningIfNecessary
|
||||
import app.lawnchair.blur.GlassBlur
|
||||
import app.lawnchair.compat.LawnchairQuickstepCompat
|
||||
import app.lawnchair.data.AppDatabase
|
||||
import app.lawnchair.data.wallpaper.service.WallpaperService
|
||||
@@ -168,9 +167,6 @@ class LawnchairLauncher : QuickstepLauncher() {
|
||||
defaultOverlay.setEnableFeed(enable)
|
||||
}.launchIn(scope = lifecycleScope)
|
||||
launcher.stateManager.addStateListener(clearSearchStateListener)
|
||||
if (prefs.glassBlurIcons.get()) {
|
||||
launcher.stateManager.addStateListener(GlassBlur.createStateListener(this))
|
||||
}
|
||||
|
||||
if (prefs.autoLaunchRoot.get()) {
|
||||
lifecycleScope.launch {
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
package app.lawnchair.blur
|
||||
|
||||
import android.content.ContextWrapper
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Path
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.View
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import com.android.launcher3.Launcher
|
||||
import com.android.launcher3.LauncherState
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer
|
||||
import com.android.launcher3.graphics.ThemeManager
|
||||
import com.android.launcher3.statemanager.StateManager
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Live blur of the wallpaper behind a home screen item, the Android equivalent of CSS
|
||||
* backdrop-filter. Items draw it right before their own content: SurfaceFlinger blurs what is
|
||||
* behind the launcher window within the region, which follows the item as it moves.
|
||||
*/
|
||||
class GlassBlur(private val view: View, private val type: Type) {
|
||||
|
||||
enum class Type { ICON, FOLDER_ICON, OPEN_FOLDER, WIDGET }
|
||||
|
||||
/**
|
||||
* Workspace item drawing a glass blur, released while the item is hidden.
|
||||
*/
|
||||
interface Host {
|
||||
fun releaseGlassBlur()
|
||||
}
|
||||
|
||||
val isEnabled: Boolean
|
||||
private val radiusPx: Int
|
||||
|
||||
// Folders and widgets get their tint from their own background
|
||||
private val tint: Int
|
||||
private var drawable: GlassBlurDrawable? = null
|
||||
private val bounds = Rect()
|
||||
private val clipBounds = RectF()
|
||||
private val launcher by lazy {
|
||||
generateSequence(view.context) { (it as? ContextWrapper)?.baseContext }
|
||||
.firstOrNull { it is Launcher } as Launcher?
|
||||
}
|
||||
|
||||
init {
|
||||
// The launcher is recreated when these settings change
|
||||
val prefs = PreferenceManager.getInstance(view.context)
|
||||
isEnabled = prefs.glassBlurIcons.get() && when (type) {
|
||||
Type.ICON -> true
|
||||
Type.FOLDER_ICON, Type.OPEN_FOLDER -> prefs.glassBlurFolders.get()
|
||||
Type.WIDGET -> prefs.glassBlurWidgets.get()
|
||||
}
|
||||
radiusPx = (prefs.glassBlurRadius.get() * view.resources.displayMetrics.density).roundToInt()
|
||||
tint = if (type == Type.ICON) {
|
||||
Color.argb((prefs.glassBlurTint.get() * 255).roundToInt(), 255, 255, 255)
|
||||
} else {
|
||||
Color.TRANSPARENT
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs behind the opaque part of an icon bitmap drawn within the given bounds.
|
||||
*/
|
||||
fun drawIcon(canvas: Canvas, iconBounds: Rect, icon: Bitmap) {
|
||||
if (!canDraw(canvas)) {
|
||||
return
|
||||
}
|
||||
val shape = GlassBlurShapes.getIconShape(icon, view) ?: return
|
||||
val size = iconBounds.width()
|
||||
// Slightly inside the icon, so the edge of the blur never shows around it
|
||||
val inset = (size * ICON_INSET_FRACTION).roundToInt()
|
||||
bounds.set(
|
||||
iconBounds.left + (shape.bounds.left * size).roundToInt() + inset,
|
||||
iconBounds.top + (shape.bounds.top * size).roundToInt() + inset,
|
||||
iconBounds.left + (shape.bounds.right * size).roundToInt() - inset,
|
||||
iconBounds.top + (shape.bounds.bottom * size).roundToInt() - inset,
|
||||
)
|
||||
draw(canvas, shape.radiusTL * size, shape.radiusTR * size, shape.radiusBL * size, shape.radiusBR * size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs behind a folder icon background, drawn with the folder shape at the given position.
|
||||
*/
|
||||
fun drawFolderIcon(canvas: Canvas, left: Int, top: Int, radius: Int) {
|
||||
if (view.parent !is ShortcutAndWidgetContainer || !canDraw(canvas)) {
|
||||
return
|
||||
}
|
||||
bounds.set(left, top, left + 2 * radius, top + 2 * radius)
|
||||
val folderShape = ThemeManager.INSTANCE.get(view.context).folderShape
|
||||
val corner = radius * GlassBlurShapes.getFolderCornerRatio(folderShape)
|
||||
draw(canvas, corner, corner, corner, corner)
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs behind an open folder background. While opening and closing, the folder is clipped
|
||||
* to a rounded rect growing from the folder icon, which the blur follows.
|
||||
*/
|
||||
fun drawOpenFolder(canvas: Canvas, background: GradientDrawable, clipPath: Path?) {
|
||||
if (!canDraw(canvas)) {
|
||||
return
|
||||
}
|
||||
bounds.set(background.bounds)
|
||||
var corner = background.cornerRadius
|
||||
if (clipPath != null) {
|
||||
clipPath.computeBounds(clipBounds, false)
|
||||
val intersects = bounds.intersect(
|
||||
clipBounds.left.roundToInt(),
|
||||
clipBounds.top.roundToInt(),
|
||||
clipBounds.right.roundToInt(),
|
||||
clipBounds.bottom.roundToInt(),
|
||||
)
|
||||
if (!intersects) {
|
||||
bounds.setEmpty()
|
||||
}
|
||||
// The reveal starts from the folder icon, about as round as a circle
|
||||
corner = min(corner, min(bounds.width(), bounds.height()) / 2f)
|
||||
}
|
||||
draw(canvas, corner, corner, corner, corner)
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs behind a widget background, with the corners the widget is clipped to.
|
||||
*/
|
||||
fun drawWidget(canvas: Canvas, backgroundBounds: Rect, cornerRadius: Float) {
|
||||
if (backgroundBounds.isEmpty || view.parent !is ShortcutAndWidgetContainer || !canDraw(canvas)) {
|
||||
return
|
||||
}
|
||||
bounds.set(backgroundBounds)
|
||||
val corner = max(cornerRadius, 0f)
|
||||
draw(canvas, corner, corner, corner, corner)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the blur region, which is registered again on the next draw.
|
||||
*/
|
||||
fun release() {
|
||||
drawable?.setVisible(false, false)
|
||||
drawable = null
|
||||
}
|
||||
|
||||
private fun canDraw(canvas: Canvas): Boolean {
|
||||
// Software canvases (e.g. icon snapshots) cannot draw render nodes
|
||||
if (!isEnabled || !canvas.isHardwareAccelerated) {
|
||||
return false
|
||||
}
|
||||
// Folders opened from the app drawer are not hidden by it
|
||||
val state = launcher?.stateManager?.state
|
||||
return type == Type.OPEN_FOLDER || state == null || !isSuppressedIn(state)
|
||||
}
|
||||
|
||||
private fun draw(canvas: Canvas, radiusTL: Float, radiusTR: Float, radiusBL: Float, radiusBR: Float) {
|
||||
val blur = drawable ?: run {
|
||||
if (view.viewRootImpl == null) {
|
||||
return
|
||||
}
|
||||
GlassBlurDrawable.create(view).also {
|
||||
it.setBlurRadius(radiusPx)
|
||||
it.setColor(tint)
|
||||
drawable = it
|
||||
}
|
||||
}
|
||||
blur.bounds = bounds
|
||||
blur.setCornerRadius(radiusTL, radiusTR, radiusBL, radiusBR)
|
||||
blur.draw(canvas)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ICON_INSET_FRACTION = 0.02f
|
||||
|
||||
/**
|
||||
* Workspace items are hidden behind the app drawer and recents. Without root, their
|
||||
* regions would also count towards the SurfaceFlinger limit of 10 blur requests on
|
||||
* screen, and disable the window background blur used by the drawer and recents.
|
||||
*/
|
||||
private fun isSuppressedIn(state: LauncherState) = state == LauncherState.ALL_APPS || state.isRecentsViewVisible
|
||||
|
||||
/**
|
||||
* Releases the blur regions of the workspace items while they are hidden.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createStateListener(launcher: Launcher) = object : StateManager.StateListener<LauncherState> {
|
||||
override fun onStateTransitionStart(toState: LauncherState) {
|
||||
if (isSuppressedIn(toState)) {
|
||||
releaseWorkspaceItems(launcher)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateTransitionComplete(finalState: LauncherState) {
|
||||
if (isSuppressedIn(finalState)) {
|
||||
releaseWorkspaceItems(launcher)
|
||||
} else {
|
||||
// Items are not redrawn on their own, while their regions are registered on draw
|
||||
launcher.workspace.mapOverItems { _, item ->
|
||||
if (item is Host) {
|
||||
item.invalidate()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseWorkspaceItems(launcher: Launcher) {
|
||||
launcher.workspace.mapOverItems { _, item ->
|
||||
(item as? Host)?.releaseGlassBlur()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 app.lawnchair.blur
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RenderNode
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.util.ArraySet
|
||||
import android.util.LongSparseArray
|
||||
import android.view.SurfaceControl
|
||||
import android.view.View
|
||||
import android.view.ViewRootImpl
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.annotation.UiThread
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/**
|
||||
* Copy of the framework BackgroundBlurDrawable that sends its blur regions to an effect layer
|
||||
* created by root (see [GlassBlurRootLayer]), placed right below the window content.
|
||||
* SurfaceFlinger ignores all the blur of an app layer past 10 blur requests on screen
|
||||
* (b/399120953), but not the blur of layers created by privileged processes. Without root, the
|
||||
* regions go to the window layer, like with the framework drawable.
|
||||
*/
|
||||
class GlassBlurDrawable private constructor(private val aggregator: Aggregator) : Drawable() {
|
||||
|
||||
private val renderNode = RenderNode("GlassBlurDrawable")
|
||||
private val paint = Paint().apply {
|
||||
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
||||
color = Color.TRANSPARENT
|
||||
isAntiAlias = true
|
||||
}
|
||||
private val rectPath = Path()
|
||||
private val tmpRadii = FloatArray(8)
|
||||
|
||||
// Confined to the UI thread. The values are copied into a BlurRegion, which lives on
|
||||
// RenderThread to avoid interference with UI thread updates.
|
||||
private var regionVisible = true
|
||||
private var blurRadius = 0
|
||||
private var cornerRadiusTL = 0f
|
||||
private var cornerRadiusTR = 0f
|
||||
private var cornerRadiusBL = 0f
|
||||
private var cornerRadiusBR = 0f
|
||||
private var regionAlpha = 255
|
||||
|
||||
// Do not update from the UI thread. This holds the latest position for this drawable. It is
|
||||
// used by the Aggregator from RenderThread to get the final position of the region sent to SF
|
||||
private val rect = Rect()
|
||||
|
||||
// HWUI only keeps a weak reference to the listener. It is called from a thread pool, and the
|
||||
// callbacks might come out of order w.r.t. the frame number, so the Aggregator applies the
|
||||
// updates on RenderThread when processing that same frame.
|
||||
private val positionUpdateListener = object : RenderNode.PositionUpdateListener {
|
||||
override fun positionChanged(frameNumber: Long, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
aggregator.onRenderNodePositionChanged(frameNumber) { rect.set(left, top, right, bottom) }
|
||||
}
|
||||
|
||||
override fun positionLost(frameNumber: Long) {
|
||||
aggregator.onRenderNodePositionChanged(frameNumber) { rect.setEmpty() }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
renderNode.addPositionUpdateListener(positionUpdateListener)
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
if (rectPath.isEmpty || !isVisible || regionAlpha == 0) {
|
||||
return
|
||||
}
|
||||
canvas.drawPath(rectPath, paint)
|
||||
canvas.drawRenderNode(renderNode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Color that will be alpha blended on top of the blur.
|
||||
*/
|
||||
fun setColor(color: Int) {
|
||||
paint.color = color
|
||||
}
|
||||
|
||||
override fun setVisible(visible: Boolean, restart: Boolean): Boolean {
|
||||
val changed = super.setVisible(visible, restart)
|
||||
if (changed) {
|
||||
regionVisible = visible
|
||||
aggregator.onBlurDrawableUpdated(this)
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
if (regionAlpha != alpha) {
|
||||
regionAlpha = alpha
|
||||
invalidateSelf()
|
||||
aggregator.onBlurDrawableUpdated(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAlpha() = regionAlpha
|
||||
|
||||
/**
|
||||
* Blur radius in pixels.
|
||||
*/
|
||||
fun setBlurRadius(radius: Int) {
|
||||
if (blurRadius != radius) {
|
||||
blurRadius = radius
|
||||
invalidateSelf()
|
||||
aggregator.onBlurDrawableUpdated(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of each corner, in pixels.
|
||||
*/
|
||||
fun setCornerRadius(topLeft: Float, topRight: Float, bottomLeft: Float, bottomRight: Float) {
|
||||
if (cornerRadiusTL != topLeft || cornerRadiusTR != topRight ||
|
||||
cornerRadiusBL != bottomLeft || cornerRadiusBR != bottomRight
|
||||
) {
|
||||
cornerRadiusTL = topLeft
|
||||
cornerRadiusTR = topRight
|
||||
cornerRadiusBL = bottomLeft
|
||||
cornerRadiusBR = bottomRight
|
||||
updatePath()
|
||||
invalidateSelf()
|
||||
aggregator.onBlurDrawableUpdated(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setBounds(left, top, right, bottom)
|
||||
renderNode.setPosition(left, top, right, bottom)
|
||||
updatePath()
|
||||
}
|
||||
|
||||
private fun updatePath() {
|
||||
// Clockwise from the top left corner
|
||||
tmpRadii.fill(cornerRadiusTL, 0, 2)
|
||||
tmpRadii.fill(cornerRadiusTR, 2, 4)
|
||||
tmpRadii.fill(cornerRadiusBR, 4, 6)
|
||||
tmpRadii.fill(cornerRadiusBL, 6, 8)
|
||||
rectPath.reset()
|
||||
if (regionAlpha == 0 || !isVisible) {
|
||||
return
|
||||
}
|
||||
val b = bounds
|
||||
rectPath.addRoundRect(
|
||||
b.left.toFloat(),
|
||||
b.top.toFloat(),
|
||||
b.right.toFloat(),
|
||||
b.bottom.toFloat(),
|
||||
tmpRadii,
|
||||
Path.Direction.CW,
|
||||
)
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
throw IllegalArgumentException("not implemented")
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getOpacity() = PixelFormat.TRANSLUCENT
|
||||
|
||||
/**
|
||||
* Keeps track of all the blur regions of a [ViewRootImpl] and sends them to SurfaceFlinger
|
||||
* along with the frame they belong to.
|
||||
*/
|
||||
private class Aggregator(private val viewRoot: ViewRootImpl) : ViewRootImpl.SurfaceChangedCallback {
|
||||
private val rtLock = Any()
|
||||
|
||||
// All the visible blur drawables, confined to the UI thread
|
||||
private val drawables = ArraySet<GlassBlurDrawable>()
|
||||
|
||||
// Guarded by rtLock
|
||||
private val frameRtUpdates = LongSparseArray<ArraySet<Runnable>>()
|
||||
private var lastFrameNumber = 0L
|
||||
private var lastFrameBlurRegions: Array<BlurRegion>? = null
|
||||
|
||||
private var tmpBlurRegionsForFrame = emptyArray<BlurRegion>()
|
||||
private var hasUiUpdates = false
|
||||
private var onPreDrawListener: ViewTreeObserver.OnPreDrawListener? = null
|
||||
|
||||
// Written on the UI thread, read on RenderThread
|
||||
@Volatile
|
||||
private var rootLayer: SurfaceControl? = null
|
||||
|
||||
// Regions sent to the window layer before the root layer arrived have to be cleared
|
||||
@Volatile
|
||||
private var clearWindowRegions = false
|
||||
|
||||
@UiThread
|
||||
private fun onRootLayerReady(layer: SurfaceControl) {
|
||||
val rootView = viewRoot.view
|
||||
if (rootView == null || !rootView.isAttachedToWindow) {
|
||||
SurfaceControl.Transaction().remove(layer).apply()
|
||||
return
|
||||
}
|
||||
rootLayer = layer
|
||||
clearWindowRegions = true
|
||||
viewRoot.addSurfaceChangedCallback(this)
|
||||
rootView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View) {}
|
||||
|
||||
override fun onViewDetachedFromWindow(v: View) {
|
||||
v.removeOnAttachStateChangeListener(this)
|
||||
viewRoot.removeSurfaceChangedCallback(this@Aggregator)
|
||||
aggregators.remove(viewRoot)
|
||||
rootLayer = null
|
||||
SurfaceControl.Transaction().remove(layer).apply()
|
||||
}
|
||||
})
|
||||
val t = SurfaceControl.Transaction()
|
||||
attachRootLayer(t)
|
||||
t.apply()
|
||||
if (hasRegions()) {
|
||||
hasUiUpdates = true
|
||||
rootView.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachRootLayer(t: SurfaceControl.Transaction) {
|
||||
val layer = rootLayer ?: return
|
||||
val windowLayer = viewRoot.surfaceControl
|
||||
if (!windowLayer.isValid) {
|
||||
return
|
||||
}
|
||||
// Below the window content, so it blurs what is behind the window (the wallpaper)
|
||||
t.reparent(layer, windowLayer).setLayer(layer, -1).show(layer)
|
||||
}
|
||||
|
||||
override fun surfaceCreated(t: SurfaceControl.Transaction) {
|
||||
attachRootLayer(t)
|
||||
}
|
||||
|
||||
override fun surfaceReplaced(t: SurfaceControl.Transaction) {
|
||||
attachRootLayer(t)
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed() {}
|
||||
|
||||
@UiThread
|
||||
fun onBlurDrawableUpdated(drawable: GlassBlurDrawable) {
|
||||
val shouldBeDrawn = drawable.regionAlpha > 0 && drawable.blurRadius > 0 &&
|
||||
drawable.regionVisible
|
||||
val isDrawn = drawables.contains(drawable)
|
||||
if (shouldBeDrawn) {
|
||||
hasUiUpdates = true
|
||||
if (!isDrawn) {
|
||||
drawables.add(drawable)
|
||||
}
|
||||
} else if (isDrawn) {
|
||||
hasUiUpdates = true
|
||||
drawables.remove(drawable)
|
||||
// The pre-draw listener only sends the regions left, even none, if a frame is drawn
|
||||
if (onPreDrawListener != null) {
|
||||
viewRoot.view?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
val rootView = viewRoot.view
|
||||
if (onPreDrawListener == null && rootView != null && hasRegions()) {
|
||||
registerPreDrawListener(rootView)
|
||||
if (hasUiUpdates) {
|
||||
rootView.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerPreDrawListener(rootView: View) {
|
||||
val listener = ViewTreeObserver.OnPreDrawListener {
|
||||
val uiUpdates = hasUiUpdates
|
||||
if (uiUpdates || hasRegions()) {
|
||||
val blurRegionsForNextFrame = getBlurRegionsCopyForRT()
|
||||
viewRoot.registerRtFrameCallback { frame ->
|
||||
synchronized(rtLock) {
|
||||
lastFrameNumber = frame
|
||||
lastFrameBlurRegions = blurRegionsForNextFrame
|
||||
handleDispatchBlurTransactionLocked(frame, blurRegionsForNextFrame, uiUpdates)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasRegions()) {
|
||||
viewRoot.view?.viewTreeObserver?.removeOnPreDrawListener(onPreDrawListener)
|
||||
onPreDrawListener = null
|
||||
}
|
||||
true
|
||||
}
|
||||
onPreDrawListener = listener
|
||||
rootView.viewTreeObserver.addOnPreDrawListener(listener)
|
||||
}
|
||||
|
||||
// Called from a thread pool
|
||||
fun onRenderNodePositionChanged(frameNumber: Long, update: Runnable) {
|
||||
// One of the blur region's position has changed, so we have to send an updated list
|
||||
// of blur regions to SurfaceFlinger for this frame.
|
||||
synchronized(rtLock) {
|
||||
val updates = frameRtUpdates.get(frameNumber)
|
||||
?: ArraySet<Runnable>().also { frameRtUpdates.put(frameNumber, it) }
|
||||
updates.add(update)
|
||||
|
||||
if (lastFrameNumber == frameNumber) {
|
||||
// The transaction for this frame has already been sent, so we have to manually
|
||||
// trigger sending a transaction here in order to apply this position update
|
||||
lastFrameBlurRegions?.let {
|
||||
handleDispatchBlurTransactionLocked(frameNumber, it, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun hasRegions() = drawables.isNotEmpty()
|
||||
|
||||
@UiThread
|
||||
private fun getBlurRegionsCopyForRT(): Array<BlurRegion> {
|
||||
if (hasUiUpdates) {
|
||||
tmpBlurRegionsForFrame = Array(drawables.size) { BlurRegion(drawables.valueAt(it)) }
|
||||
hasUiUpdates = false
|
||||
}
|
||||
return tmpBlurRegionsForFrame
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on RenderThread with rtLock held.
|
||||
*
|
||||
* @return the regions to send to SurfaceFlinger for this frame, or null if none changed
|
||||
*/
|
||||
private fun getBlurRegionsForFrameLocked(
|
||||
frameNumber: Long,
|
||||
blurRegionsForFrame: Array<BlurRegion>,
|
||||
forceUpdate: Boolean,
|
||||
): Array<FloatArray>? {
|
||||
if (!forceUpdate && (frameRtUpdates.size() == 0 || frameRtUpdates.keyAt(0) > frameNumber)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// frameRtUpdates holds position updates coming from a thread pool span from
|
||||
// RenderThread. At this point, all position updates for frame frameNumber should
|
||||
// have been added to frameRtUpdates.
|
||||
// Here, we apply all updates for frames <= frameNumber in case some previous update
|
||||
// has been missed. This also protects frameRtUpdates from memory leaks.
|
||||
while (frameRtUpdates.size() != 0 && frameRtUpdates.keyAt(0) <= frameNumber) {
|
||||
val frameUpdates = frameRtUpdates.valueAt(0)
|
||||
frameRtUpdates.removeAt(0)
|
||||
frameUpdates.forEach { it.run() }
|
||||
}
|
||||
|
||||
return blurRegionsForFrame
|
||||
.filter { !it.rect.isEmpty }
|
||||
.map { it.toFloatArray() }
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches all blur regions if there are any UI or position updates for that frame.
|
||||
*/
|
||||
private fun handleDispatchBlurTransactionLocked(
|
||||
frameNumber: Long,
|
||||
blurRegions: Array<BlurRegion>,
|
||||
forceUpdate: Boolean,
|
||||
) {
|
||||
val regions = getBlurRegionsForFrameLocked(frameNumber, blurRegions, forceUpdate)
|
||||
?: return
|
||||
val layer = rootLayer
|
||||
if (layer == null) {
|
||||
viewRoot.dispatchBlurRegions(regions, frameNumber)
|
||||
return
|
||||
}
|
||||
val t = SurfaceControl.Transaction()
|
||||
t.setBlurRegions(layer, regions)
|
||||
if (clearWindowRegions) {
|
||||
clearWindowRegions = false
|
||||
val windowLayer = viewRoot.surfaceControl
|
||||
if (windowLayer.isValid) {
|
||||
t.setBlurRegions(windowLayer, emptyArray())
|
||||
}
|
||||
}
|
||||
viewRoot.mergeWithNextTransaction(t, frameNumber)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@UiThread
|
||||
fun get(viewRoot: ViewRootImpl, context: Context): Aggregator = aggregators.getOrPut(viewRoot) {
|
||||
Aggregator(viewRoot).also { aggregator ->
|
||||
GlassBlurRootLayer.request(context, aggregator::onRootLayerReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of a blur drawable, confined to RenderThread.
|
||||
*/
|
||||
private class BlurRegion(drawable: GlassBlurDrawable) {
|
||||
private val blurRadius = drawable.blurRadius
|
||||
private val cornerRadiusTL = drawable.cornerRadiusTL
|
||||
private val cornerRadiusTR = drawable.cornerRadiusTR
|
||||
private val cornerRadiusBL = drawable.cornerRadiusBL
|
||||
private val cornerRadiusBR = drawable.cornerRadiusBR
|
||||
private val alpha = drawable.regionAlpha / 255f
|
||||
val rect = drawable.rect
|
||||
|
||||
/**
|
||||
* Serializes this region into the layout expected by
|
||||
* SurfaceControl.Transaction#setBlurRegions, which has separate X/Y corner radii since
|
||||
* Android 17.
|
||||
*/
|
||||
fun toFloatArray(): FloatArray {
|
||||
val position = floatArrayOf(
|
||||
blurRadius.toFloat(),
|
||||
alpha,
|
||||
rect.left.toFloat(),
|
||||
rect.top.toFloat(),
|
||||
rect.right.toFloat(),
|
||||
rect.bottom.toFloat(),
|
||||
)
|
||||
val corners = if (Build.VERSION.SDK_INT >= 37) {
|
||||
floatArrayOf(
|
||||
cornerRadiusTL,
|
||||
cornerRadiusTL,
|
||||
cornerRadiusTR,
|
||||
cornerRadiusTR,
|
||||
cornerRadiusBL,
|
||||
cornerRadiusBL,
|
||||
cornerRadiusBR,
|
||||
cornerRadiusBR,
|
||||
)
|
||||
} else {
|
||||
floatArrayOf(cornerRadiusTL, cornerRadiusTR, cornerRadiusBL, cornerRadiusBR)
|
||||
}
|
||||
return position + corners
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val aggregators = WeakHashMap<ViewRootImpl, Aggregator>()
|
||||
|
||||
/**
|
||||
* Creates a blur region for the window of the given view, which must be attached.
|
||||
*/
|
||||
@UiThread
|
||||
fun create(view: View): GlassBlurDrawable = GlassBlurDrawable(Aggregator.get(view.viewRootImpl, view.context))
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package app.lawnchair.blur
|
||||
|
||||
import android.content.Context
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import android.view.SurfaceControl
|
||||
import androidx.annotation.UiThread
|
||||
import app.lawnchair.root.RootHelperBackend
|
||||
import app.lawnchair.root.RootHelperManager
|
||||
import app.lawnchair.root.RootNotAvailableException
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Hands out effect layers created by the root helper. SurfaceFlinger never ignores the blur of
|
||||
* layers created by privileged processes, so their blur regions are exempt from the limit of 10
|
||||
* blur requests that applies to app layers.
|
||||
*/
|
||||
internal object GlassBlurRootLayer {
|
||||
private const val TAG = "LC-GlassBlur"
|
||||
|
||||
private val scope = MainScope() + CoroutineName("GlassBlurRootLayer")
|
||||
|
||||
/**
|
||||
* Calls back on the main thread with a new layer. Never calls back without root.
|
||||
*/
|
||||
@UiThread
|
||||
fun request(context: Context, callback: (SurfaceControl) -> Unit) {
|
||||
val rootHelperManager = RootHelperManager.INSTANCE.get(context)
|
||||
scope.launch {
|
||||
try {
|
||||
if (!rootHelperManager.isAvailable()) {
|
||||
Log.i(TAG, "Root not available, blur limited to 10 regions on screen")
|
||||
return@launch
|
||||
}
|
||||
val rootHelper = rootHelperManager.getService()
|
||||
val layer = withContext(Dispatchers.IO) {
|
||||
rootHelper.createGlassBlurLayer().getParcelable(
|
||||
RootHelperBackend.GLASS_BLUR_LAYER_KEY,
|
||||
SurfaceControl::class.java,
|
||||
)
|
||||
}
|
||||
if (layer != null) {
|
||||
callback(layer)
|
||||
}
|
||||
} catch (_: RootNotAvailableException) {
|
||||
} catch (e: RemoteException) {
|
||||
Log.w(TAG, "Unable to create the blur layer", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package app.lawnchair.blur
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Region
|
||||
import android.view.View
|
||||
import androidx.annotation.UiThread
|
||||
import com.android.launcher3.graphics.ShapeDelegate
|
||||
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
|
||||
import com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.WeakHashMap
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Shapes of the blur regions. SurfaceFlinger only supports rounded rects, with a radius per
|
||||
* corner, so these are the closest fit to the actual shapes.
|
||||
*/
|
||||
internal object GlassBlurShapes {
|
||||
// Icon shape pixels are at least this opaque, translucent icon packs go down to ~0.2 alpha...
|
||||
private const val ICON_ALPHA_THRESHOLD = 32
|
||||
|
||||
// ...and not black, to leave out the shadow drawn around icon pack icons
|
||||
private const val ICON_MIN_CHANNEL = 24
|
||||
|
||||
// On a corner rounded with radius r, the first point of the diagonal inside the shape is
|
||||
// r * (1 - 1/sqrt(2)) away from the corner along each axis
|
||||
private val DIAGONAL_TO_RADIUS = 1f / (1f - sqrt(0.5f))
|
||||
|
||||
/**
|
||||
* Blur region of an icon: the bounding box of its opaque part and the radius of each
|
||||
* corner, as fractions of the bitmap width.
|
||||
*/
|
||||
class IconShape(val bounds: RectF, val radiusTL: Float, val radiusTR: Float, val radiusBL: Float, val radiusBR: Float)
|
||||
|
||||
// Confined to the UI thread
|
||||
private val iconShapes = WeakHashMap<Bitmap, IconShape>()
|
||||
private val viewsWaitingForShape = WeakHashMap<Bitmap, MutableList<WeakReference<View>>>()
|
||||
|
||||
private var folderShape: ShapeDelegate? = null
|
||||
private var folderCornerRatio = 0f
|
||||
|
||||
/**
|
||||
* Returns the blur region of an icon. The first time, it returns null and reads the pixels
|
||||
* in the background, then redraws the view.
|
||||
*/
|
||||
@UiThread
|
||||
fun getIconShape(icon: Bitmap, view: View): IconShape? {
|
||||
iconShapes[icon]?.let { return it }
|
||||
viewsWaitingForShape[icon]?.let {
|
||||
it.add(WeakReference(view))
|
||||
return null
|
||||
}
|
||||
viewsWaitingForShape[icon] = mutableListOf(WeakReference(view))
|
||||
THREAD_POOL_EXECUTOR.execute {
|
||||
val shape = computeIconShape(icon)
|
||||
MAIN_EXECUTOR.execute {
|
||||
iconShapes[icon] = shape
|
||||
viewsWaitingForShape.remove(icon)?.forEach { it.get()?.invalidate() }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeIconShape(icon: Bitmap): IconShape {
|
||||
// Hardware bitmaps cannot be read directly
|
||||
val readable = if (icon.config == Bitmap.Config.HARDWARE) {
|
||||
icon.copy(Bitmap.Config.ARGB_8888, false)
|
||||
} else {
|
||||
icon
|
||||
}
|
||||
val w = readable.width
|
||||
val h = readable.height
|
||||
val pixels = IntArray(w * h)
|
||||
readable.getPixels(pixels, 0, w, 0, 0, w, h)
|
||||
if (readable !== icon) {
|
||||
readable.recycle()
|
||||
}
|
||||
|
||||
var left = w
|
||||
var top = h
|
||||
var right = -1
|
||||
var bottom = -1
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
if (isShapePixel(pixels[y * w + x])) {
|
||||
left = min(left, x)
|
||||
right = max(right, x)
|
||||
top = min(top, y)
|
||||
bottom = max(bottom, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (right < 0) {
|
||||
return IconShape(RectF(), 0f, 0f, 0f, 0f)
|
||||
}
|
||||
val maxRadius = min(right - left + 1, bottom - top + 1) / 2
|
||||
fun cornerRadius(x: Int, y: Int, dx: Int, dy: Int): Float {
|
||||
for (k in 0 until maxRadius) {
|
||||
if (isShapePixel(pixels[(y + dy * k) * w + x + dx * k])) {
|
||||
return min(k * DIAGONAL_TO_RADIUS, maxRadius.toFloat()) / w
|
||||
}
|
||||
}
|
||||
return maxRadius.toFloat() / w
|
||||
}
|
||||
return IconShape(
|
||||
RectF(left.toFloat() / w, top.toFloat() / h, (right + 1).toFloat() / w, (bottom + 1).toFloat() / h),
|
||||
radiusTL = cornerRadius(left, top, 1, 1),
|
||||
radiusTR = cornerRadius(right, top, -1, 1),
|
||||
radiusBL = cornerRadius(left, bottom, 1, -1),
|
||||
radiusBR = cornerRadius(right, bottom, -1, -1),
|
||||
)
|
||||
}
|
||||
|
||||
private fun isShapePixel(argb: Int) = Color.alpha(argb) >= ICON_ALPHA_THRESHOLD &&
|
||||
max(Color.red(argb), max(Color.green(argb), Color.blue(argb))) >= ICON_MIN_CHANNEL
|
||||
|
||||
/**
|
||||
* Returns the corner radius of the folder shape, as a fraction of half its edge.
|
||||
*/
|
||||
@UiThread
|
||||
fun getFolderCornerRatio(shape: ShapeDelegate): Float {
|
||||
if (shape == folderShape) {
|
||||
return folderCornerRatio
|
||||
}
|
||||
val ratio = if (shape is ShapeDelegate.RoundedSquare) {
|
||||
shape.radiusRatio
|
||||
} else {
|
||||
val path = Path().also { shape.addToPath(it, 0f, 0f, 50f) }
|
||||
val region = Region().apply { setPath(path, Region(0, 0, 100, 100)) }
|
||||
var k = 0
|
||||
while (k < 50 && !region.contains(k, k)) {
|
||||
k++
|
||||
}
|
||||
min(1f, k * DIAGONAL_TO_RADIUS / 50f)
|
||||
}
|
||||
folderShape = shape
|
||||
folderCornerRatio = ratio
|
||||
return ratio
|
||||
}
|
||||
}
|
||||
@@ -76,11 +76,6 @@ class PreferenceManager @Inject constructor(
|
||||
val wrapAdaptiveIcons = BoolPref("prefs_wrapAdaptive", true)
|
||||
val transparentIconBackground = BoolPref("prefs_transparentIconBackground", false)
|
||||
val shadowBGIcons = BoolPref("pref_shadowBGIcons", true)
|
||||
val glassBlurIcons = BoolPref("pref_glassBlurIcons", false, recreate)
|
||||
val glassBlurRadius = IntPref("pref_glassBlurRadius", 24, recreate)
|
||||
val glassBlurTint = FloatPref("pref_glassBlurTint", 0.2f, recreate)
|
||||
val glassBlurFolders = BoolPref("pref_glassBlurFolders", true, recreate)
|
||||
val glassBlurWidgets = BoolPref("pref_glassBlurWidgets", true, recreate)
|
||||
val addIconToHome = BoolPref("pref_add_icon_to_home", true)
|
||||
|
||||
private val isPhone: Boolean get() = deviceType == InvariantDeviceProfile.TYPE_PHONE
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
package app.lawnchair.root;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.os.SystemClock;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceSession;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class RootHelperBackend extends IRootHelper.Stub {
|
||||
public static final String GLASS_BLUR_LAYER_KEY = "layer";
|
||||
|
||||
// The client holds its own reference to the layer once the reply is parceled
|
||||
private static final long GLASS_BLUR_LAYER_RELEASE_DELAY_MS = 10_000;
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public RootHelperBackend(Context context) {
|
||||
mContext = context;
|
||||
@@ -32,17 +21,4 @@ public class RootHelperBackend extends IRootHelper.Stub {
|
||||
public void goToSleep() {
|
||||
getPowerManager().goToSleep(SystemClock.uptimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle createGlassBlurLayer() {
|
||||
// SurfaceFlinger records the permissions of the creating process on the layer
|
||||
SurfaceControl layer = new SurfaceControl.Builder(new SurfaceSession())
|
||||
.setName("LawnchairGlassBlur")
|
||||
.setEffectLayer()
|
||||
.build();
|
||||
Bundle result = new Bundle();
|
||||
result.putParcelable(GLASS_BLUR_LAYER_KEY, layer);
|
||||
mMainHandler.postDelayed(layer::release, GLASS_BLUR_LAYER_RELEASE_DELAY_MS);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ class RootHelperManager(private val context: Context) {
|
||||
private val prefs = PreferenceManager.getInstance(context)
|
||||
private val isAvailableDeferred by lazy {
|
||||
scope.async(Dispatchers.IO) {
|
||||
// Shell.rootAccess() is false until a root shell has been created
|
||||
Shell.getShell().isRoot
|
||||
Shell.rootAccess()
|
||||
}
|
||||
}
|
||||
private var rootHelperDeferred: Deferred<IRootHelper>? = null
|
||||
@@ -47,8 +46,7 @@ class RootHelperManager(private val context: Context) {
|
||||
}
|
||||
|
||||
private suspend fun bindImpl(onDisconnected: () -> Unit): IRootHelper {
|
||||
// RootService.bind() can only be called on the main thread
|
||||
return withContext(Dispatchers.Main) {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val intent = Intent(context, RootHelper::class.java)
|
||||
suspendCoroutine {
|
||||
val connection = object : ServiceConnection {
|
||||
|
||||
@@ -160,48 +160,6 @@ fun GeneralPreferences(modifier: Modifier = Modifier) {
|
||||
)
|
||||
}
|
||||
|
||||
val glassBlurIcons = prefs.glassBlurIcons.getAdapter()
|
||||
|
||||
PreferenceGroup(heading = stringResource(id = R.string.glass_blur_label)) {
|
||||
SwitchPreference(
|
||||
adapter = glassBlurIcons,
|
||||
label = stringResource(id = R.string.glass_blur_icons_label),
|
||||
description = stringResource(id = R.string.glass_blur_icons_description),
|
||||
)
|
||||
ExpandAndShrink(visible = glassBlurIcons.state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.glass_blur_radius_label),
|
||||
adapter = prefs.glassBlurRadius.getAdapter(),
|
||||
step = 2,
|
||||
valueRange = 4..64,
|
||||
showUnit = "dp",
|
||||
)
|
||||
}
|
||||
ExpandAndShrink(visible = glassBlurIcons.state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.glass_blur_tint_label),
|
||||
adapter = prefs.glassBlurTint.getAdapter(),
|
||||
valueRange = 0F..0.6F,
|
||||
step = 0.05f,
|
||||
showAsPercentage = true,
|
||||
)
|
||||
}
|
||||
ExpandAndShrink(visible = glassBlurIcons.state.value) {
|
||||
SwitchPreference(
|
||||
adapter = prefs.glassBlurFolders.getAdapter(),
|
||||
label = stringResource(id = R.string.glass_blur_folders_label),
|
||||
description = stringResource(id = R.string.glass_blur_folders_description),
|
||||
)
|
||||
}
|
||||
ExpandAndShrink(visible = glassBlurIcons.state.value) {
|
||||
SwitchPreference(
|
||||
adapter = prefs.glassBlurWidgets.getAdapter(),
|
||||
label = stringResource(id = R.string.glass_blur_widgets_label),
|
||||
description = stringResource(id = R.string.glass_blur_widgets_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val accentColorAdapter = prefs2.accentColor.getAdapter()
|
||||
val showColorStyle = !(Utilities.ATLEAST_S && accentColorAdapter.state.value == ColorOption.SystemAccent) ||
|
||||
!Utilities.ATLEAST_S
|
||||
|
||||
@@ -117,7 +117,6 @@ import app.lawnchair.preferences.PreferenceManager;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
import app.lawnchair.util.LawnchairUtilsKt;
|
||||
import app.lawnchair.animation.PhysicsAnimator;
|
||||
import app.lawnchair.blur.GlassBlur;
|
||||
|
||||
/**
|
||||
* TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
|
||||
@@ -125,7 +124,7 @@ import app.lawnchair.blur.GlassBlur;
|
||||
* too aggressive.
|
||||
*/
|
||||
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
FloatingIconViewCompanion, DraggableView, Reorderable, GlassBlur.Host {
|
||||
FloatingIconViewCompanion, DraggableView, Reorderable {
|
||||
|
||||
public static final String TAG = "BubbleTextView";
|
||||
|
||||
@@ -225,10 +224,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
private final Rect mRunningAppIconBounds = new Rect();
|
||||
private RunningAppState mRunningAppState;
|
||||
|
||||
// LC-Note: live blur of the wallpaper behind workspace icons
|
||||
private final GlassBlur mGlassBlur = new GlassBlur(this, GlassBlur.Type.ICON);
|
||||
private final Rect mGlassBlurBounds = new Rect();
|
||||
|
||||
@ViewDebug.ExportedProperty(category = "launcher")
|
||||
private int mLineIndicatorColor;
|
||||
@ViewDebug.ExportedProperty(category = "launcher")
|
||||
@@ -815,9 +810,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
if (mIcon != null) {
|
||||
mIcon.setVisible(isVisible, false);
|
||||
}
|
||||
if (!isVisible) {
|
||||
releaseGlassBlur();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -929,40 +921,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
drawGlassBlurIfNecessary(canvas);
|
||||
super.onDraw(canvas);
|
||||
drawDotIfNecessary(canvas);
|
||||
drawRunningAppIndicatorIfNecessary(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
releaseGlassBlur();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseGlassBlur() {
|
||||
mGlassBlur.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* LC-Note: draws the blur of the wallpaper behind the icon, before the icon itself.
|
||||
*/
|
||||
protected void drawGlassBlurIfNecessary(Canvas canvas) {
|
||||
if (!mGlassBlur.isEnabled() || mDisplay != DISPLAY_WORKSPACE || mIcon == null
|
||||
|| !mIsIconVisible) {
|
||||
return;
|
||||
}
|
||||
getIconBounds(mGlassBlurBounds);
|
||||
// The canvas is translated by the scroll position, unlike the icon bounds
|
||||
int scrollX = getScrollX();
|
||||
int scrollY = getScrollY();
|
||||
canvas.translate(scrollX, scrollY);
|
||||
mGlassBlur.drawIcon(canvas, mGlassBlurBounds, mIcon.bitmapInfo.icon);
|
||||
canvas.translate(-scrollX, -scrollY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the notification dot in the top right corner of the icon bounds.
|
||||
*
|
||||
|
||||
@@ -138,7 +138,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
import app.lawnchair.blur.GlassBlur;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
import app.lawnchair.theme.color.ColorOption;
|
||||
import app.lawnchair.theme.color.tokens.ColorTokens;
|
||||
@@ -297,9 +296,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||
|
||||
private @NonNull GradientDrawable mBackground;
|
||||
|
||||
// LC-Note: live blur of the wallpaper behind the folder background
|
||||
private final GlassBlur mGlassBlur = new GlassBlur(this, GlassBlur.Type.OPEN_FOLDER);
|
||||
|
||||
PreferenceManager2 preferenceManager2;
|
||||
|
||||
/**
|
||||
@@ -662,7 +658,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mFolderName.removeOnFocusChangeListener(this);
|
||||
mGlassBlur.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2051,8 +2046,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
// LC-Note: the blur follows the clip path of the reveal animation
|
||||
mGlassBlur.drawOpenFolder(canvas, mBackground, mClipPath);
|
||||
if (mClipPath != null) {
|
||||
int count = canvas.save();
|
||||
canvas.clipPath(mClipPath);
|
||||
|
||||
@@ -92,13 +92,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import app.lawnchair.blur.GlassBlur;
|
||||
|
||||
/**
|
||||
* An icon that can appear on in the workspace representing an {@link Folder}.
|
||||
*/
|
||||
public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion,
|
||||
DraggableView, Reorderable, GlassBlur.Host {
|
||||
DraggableView, Reorderable {
|
||||
|
||||
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
|
||||
@Thunk ActivityContext mActivity;
|
||||
@@ -120,9 +118,6 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion
|
||||
PreviewBackground mBackground = new PreviewBackground(getContext());
|
||||
private boolean mBackgroundIsVisible = true;
|
||||
|
||||
// LC-Note: live blur of the wallpaper behind the folder icon background
|
||||
private final GlassBlur mGlassBlur = new GlassBlur(this, GlassBlur.Type.FOLDER_ICON);
|
||||
|
||||
FolderGridOrganizer mPreviewVerifier;
|
||||
ClippedFolderIconLayoutRule mPreviewLayoutRule;
|
||||
private PreviewItemManager mPreviewItemManager;
|
||||
@@ -606,8 +601,6 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion
|
||||
mPreviewItemManager.recomputePreviewDrawingParams();
|
||||
|
||||
if (!mBackground.drawingDelegated()) {
|
||||
mGlassBlur.drawFolderIcon(canvas, mBackground.getOffsetX(), mBackground.getOffsetY(),
|
||||
mBackground.getScaledRadius());
|
||||
mBackground.drawBackground(canvas);
|
||||
}
|
||||
|
||||
@@ -622,17 +615,6 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion
|
||||
drawDot(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
releaseGlassBlur();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseGlassBlur() {
|
||||
mGlassBlur.release();
|
||||
}
|
||||
|
||||
public void drawDot(Canvas canvas) {
|
||||
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
|
||||
Rect iconBounds = mDotParams.iconBounds;
|
||||
|
||||
@@ -113,9 +113,6 @@ public class DoubleShadowBubbleTextView extends BubbleTextView {
|
||||
}
|
||||
int alpha = Color.alpha(getCurrentTextColor());
|
||||
|
||||
// LC-Note: this path skips BubbleTextView#onDraw, which draws the glass blur
|
||||
drawGlassBlurIfNecessary(canvas);
|
||||
|
||||
// We enhance the shadow by drawing the shadow twice
|
||||
getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0,
|
||||
getTextShadowColor(mShadowInfo.getAmbientShadowColor(), alpha));
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.launcher3.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -30,13 +29,10 @@ import androidx.annotation.UiThread;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.Executors;
|
||||
|
||||
import app.lawnchair.blur.GlassBlur;
|
||||
|
||||
/**
|
||||
* Launcher AppWidgetHostView with support for rounded corners and a fallback View.
|
||||
*/
|
||||
public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView implements LocalColorExtractor.Listener,
|
||||
GlassBlur.Host {
|
||||
public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView implements LocalColorExtractor.Listener {
|
||||
|
||||
private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() {
|
||||
@Override
|
||||
@@ -69,10 +65,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
|
||||
// Lawnchair: LocalColorExtractor
|
||||
private final LocalColorExtractor mColorExtractor;
|
||||
|
||||
// LC-Note: live blur of the wallpaper behind the widget background
|
||||
private final GlassBlur mGlassBlur = new GlassBlur(this, GlassBlur.Type.WIDGET);
|
||||
private final Rect mGlassBlurBounds = new Rect();
|
||||
|
||||
public BaseLauncherAppWidgetHostView(Context context) {
|
||||
super(context);
|
||||
|
||||
@@ -107,27 +99,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mColorExtractor.setListener(null);
|
||||
releaseGlassBlur();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseGlassBlur() {
|
||||
mGlassBlur.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
if (canDrawGlassBlur()) {
|
||||
mGlassBlur.drawWidget(canvas, mGlassBlurBounds, mEnforcedCornerRadius);
|
||||
}
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* LC-Note: placeholders draw their content before the glass blur would punch through it.
|
||||
*/
|
||||
protected boolean canDrawGlassBlur() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,15 +110,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
|
||||
}
|
||||
|
||||
enforceRoundedCorners();
|
||||
if (mGlassBlur.isEnabled()) {
|
||||
View background = RoundedCornerEnforcement.findBackground(this);
|
||||
if (background == null) {
|
||||
mGlassBlurBounds.setEmpty();
|
||||
} else {
|
||||
RoundedCornerEnforcement.computeRoundedRectangle(this, background,
|
||||
mGlassBlurBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
||||
@@ -449,11 +449,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDrawGlassBlur() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mPreviewBitmap != null
|
||||
|
||||
Reference in new issue
Block a user