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

Commit ced15907 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Fixing surface blur when using multiple surfaces

Separating the depth controller logic into a base
class so that different controllers can be used
for different surfaces

Bug: 236780815
Test: Verified locally
Change-Id: I2bd7ed50438453d6e41c73c8001a0d6a73091653
parent 3e64f943
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -110,7 +110,7 @@ import java.util.stream.Stream;
 */
public abstract class BaseQuickstepLauncher extends Launcher {

    private DepthController mDepthController = new DepthController(this);
    private DepthController mDepthController;
    private QuickstepTransitionManager mAppTransitionManager;

    /**
@@ -247,7 +247,6 @@ public abstract class BaseQuickstepLauncher extends Launcher {
    @Override
    public void onScrollChanged(float progress) {
        super.onScrollChanged(progress);
        mDepthController.onOverlayScrollChanged(progress);
        onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX);
    }

@@ -345,6 +344,7 @@ public abstract class BaseQuickstepLauncher extends Launcher {
        mAppTransitionManager.registerRemoteTransitions();

        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
        mDepthController = new DepthController(this);
    }

    private void onTISConnected(TISBinder binder) {
+42 −46
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
@@ -95,6 +96,7 @@ import androidx.core.graphics.ColorUtils;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -145,8 +147,6 @@ import java.util.List;
 */
public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {

    private static final String TAG = "QuickstepTransition";

    private static final boolean ENABLE_SHELL_STARTING_SURFACE =
            SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);

@@ -1066,14 +1066,15 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
    private ObjectAnimator getBackgroundAnimator() {
        // When launching an app from overview that doesn't map to a task, we still want to just
        // blur the wallpaper instead of the launcher surface as well
        boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
        DepthController depthController = mLauncher.getDepthController();
        boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW
                && BlurUtils.supportsBlursOnWindows();

        MyDepthController depthController = new MyDepthController(mLauncher);
        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
                        BACKGROUND_APP.getDepth(mLauncher))
                .setDuration(APP_LAUNCH_DURATION);

        if (allowBlurringLauncher) {
            final SurfaceControl dimLayer;
            if (BlurUtils.supportsBlursOnWindows()) {
            // Create a temporary effect layer, that lives on top of launcher, so we can apply
            // the blur to it. The EffectLayer will be fullscreen, which will help with caching
            // optimizations on the SurfaceFlinger side:
@@ -1084,36 +1085,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
            SurfaceControl parent = viewRootImpl != null
                    ? viewRootImpl.getSurfaceControl()
                    : null;
                dimLayer = new SurfaceControl.Builder()
            SurfaceControl dimLayer = new SurfaceControl.Builder()
                    .setName("Blur layer")
                    .setParent(parent)
                    .setOpaque(false)
                    .setHidden(false)
                    .setEffectLayer()
                    .build();
            } else {
                dimLayer = null;
            }

            depthController.setSurface(dimLayer);
            backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    depthController.setIsInLaunchTransition(true);
            backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() ->
                    new SurfaceControl.Transaction().remove(dimLayer).apply()));
        }

                @Override
                public void onAnimationEnd(Animator animation) {
                    depthController.setIsInLaunchTransition(false);
                    depthController.setSurface(null);
                    if (dimLayer != null) {
                        new SurfaceControl.Transaction()
                                .remove(dimLayer)
                                .apply();
                    }
                }
            });
        }
        return backgroundRadiusAnim;
    }

@@ -1958,4 +1941,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
            return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
        }
    }

    private static class MyDepthController extends DepthController {
        MyDepthController(Launcher l) {
            super(l);
            setCrossWindowBlursEnabled(
                    CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled());
        }

        @Override
        public void setSurface(SurfaceControl surface) {
            super.setSurface(surface);
        }
    }
}
+27 −208
Original line number Diff line number Diff line
@@ -23,13 +23,8 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTR
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.WallpaperManager;
import android.os.IBinder;
import android.os.SystemProperties;
import android.util.FloatProperty;
import android.view.AttachedSurfaceControl;
import android.view.CrossWindowBlurListeners;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -37,12 +32,11 @@ import android.view.ViewTreeObserver;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.systemui.shared.system.BlurUtils;
import com.android.quickstep.util.BaseDepthController;

import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -50,23 +44,9 @@ import java.util.function.Consumer;
/**
 * Controls blur and wallpaper zoom, for the Launcher surface only.
 */
public class DepthController implements StateHandler<LauncherState>,
public class DepthController extends BaseDepthController implements StateHandler<LauncherState>,
        BaseActivity.MultiWindowModeChangedListener {

    private static final boolean OVERLAY_SCROLL_ENABLED = false;
    public static final FloatProperty<DepthController> DEPTH =
            new FloatProperty<DepthController>("depth") {
                @Override
                public void setValue(DepthController depthController, float depth) {
                    depthController.setDepth(depth);
                }

                @Override
                public Float get(DepthController depthController) {
                    return depthController.mDepth;
                }
            };

    /**
     * A property that updates the background blur within a given range of values (ie. even if the
     * animator goes beyond 0..1, the interpolated value will still be bounded).
@@ -92,96 +72,46 @@ public class DepthController implements StateHandler<LauncherState>,
        }
    }

    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
            new ViewTreeObserver.OnDrawListener() {
                @Override
                public void onDraw() {
                    View view = mLauncher.getDragLayer();
                    ViewRootImpl viewRootImpl = view.getViewRootImpl();
                    boolean applied = setSurface(
                            viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
                    if (!applied) {
                        dispatchTransactionSurface(mDepth);
                    }
                    view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
                }
            };
    private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;

    private final Consumer<Boolean> mCrossWindowBlurListener = new Consumer<Boolean>() {
        @Override
        public void accept(Boolean enabled) {
            mCrossWindowBlursEnabled = enabled;
            dispatchTransactionSurface(mDepth);
        }
    };
    private final Consumer<Boolean> mCrossWindowBlurListener = this::setCrossWindowBlursEnabled;

    private final Runnable mOpaquenessListener = new Runnable() {
        @Override
        public void run() {
            dispatchTransactionSurface(mDepth);
        }
    };
    private final Runnable mOpaquenessListener = this::applyDepthAndBlur;

    private final Launcher mLauncher;
    /**
     * Blur radius when completely zoomed out, in pixels.
     */
    private int mMaxBlurRadius;
    private boolean mCrossWindowBlursEnabled;
    private WallpaperManager mWallpaperManager;
    private SurfaceControl mSurface;
    /**
     * How visible the -1 overlay is, from 0 to 1.
     */
    private float mOverlayScrollProgress;
    /**
     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
     */
    private float mDepth;
    /**
     * Last blur value, in pixels, that was applied.
     * For debugging purposes.
     */
    private int mCurrentBlur;
    /**
     * If we're launching and app and should not be blurring the screen for performance reasons.
     */
    private boolean mBlurDisabledForAppLaunch;
    /**
     * If we requested early wake-up offsets to SurfaceFlinger.
     */
    private boolean mInEarlyWakeUp;


    // Workaround for animating the depth when multiwindow mode changes.
    private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;

    // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
    // marking the launcher surface as opaque.  Only used in certain Launcher states.
    private boolean mHasContentBehindLauncher;

    private View.OnAttachStateChangeListener mOnAttachListener;

    public DepthController(Launcher l) {
        mLauncher = l;
        super(l);
    }

    private void ensureDependencies() {
        if (mWallpaperManager == null) {
            mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
            mWallpaperManager = mLauncher.getSystemService(WallpaperManager.class);
    private void onLauncherDraw() {
        View view = mLauncher.getDragLayer();
        ViewRootImpl viewRootImpl = view.getViewRootImpl();
        setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
        view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener));
    }

    private void ensureDependencies() {
        if (mLauncher.getRootView() != null && mOnAttachListener == null) {
            View rootView = mLauncher.getRootView();
            mOnAttachListener = new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View view) {
                    CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
                            mCrossWindowBlurListener);
                    mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);

                    // To handle the case where window token is invalid during last setDepth call.
                    IBinder windowToken = mLauncher.getRootView().getWindowToken();
                    if (windowToken != null) {
                        mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
                    }
                    onAttached();
                    applyDepthAndBlur();
                }

                @Override
@@ -190,21 +120,11 @@ public class DepthController implements StateHandler<LauncherState>,
                    mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
                }
            };
            mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
            if (mLauncher.getRootView().isAttachedToWindow()) {
                onAttached();
            }
            rootView.addOnAttachStateChangeListener(mOnAttachListener);
            if (rootView.isAttachedToWindow()) {
                mOnAttachListener.onViewAttachedToWindow(rootView);
            }
        }

    private void onAttached() {
        CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
                mCrossWindowBlurListener);
        mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
    }

    public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
        mHasContentBehindLauncher = hasContentBehindLauncher;
    }

    /**
@@ -219,26 +139,6 @@ public class DepthController implements StateHandler<LauncherState>,
        }
    }

    /**
     * Sets the specified app target surface to apply the blur to.
     * @return true when surface was valid and transaction was dispatched.
     */
    public boolean setSurface(SurfaceControl surface) {
        // Set launcher as the SurfaceControl when we don't need an external target anymore.
        if (surface == null) {
            ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
            surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
        }
        if (mSurface != surface) {
            mSurface = surface;
            if (surface != null) {
                dispatchTransactionSurface(mDepth);
                return true;
            }
        }
        return false;
    }

    @Override
    public void setState(LauncherState toState) {
        if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) {
@@ -249,7 +149,7 @@ public class DepthController implements StateHandler<LauncherState>,
        if (Float.compare(mDepth, toDepth) != 0) {
            setDepth(toDepth);
        } else if (toState == LauncherState.OVERVIEW) {
            dispatchTransactionSurface(mDepth);
            applyDepthAndBlur();
        } else if (toState == LauncherState.BACKGROUND_APP) {
            mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
        }
@@ -269,90 +169,10 @@ public class DepthController implements StateHandler<LauncherState>,
        }
    }

    /**
     * If we're launching an app from the home screen.
     */
    public void setIsInLaunchTransition(boolean inLaunchTransition) {
        boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true);
        mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled;
        if (!inLaunchTransition) {
            // Reset depth at the end of the launch animation, so the wallpaper won't be
            // zoomed out if an app crashes.
            setDepth(0f);
        }
    }

    private void setDepth(float depth) {
        depth = Utilities.boundToRange(depth, 0, 1);
        // Round out the depth to dedupe frequent, non-perceptable updates
        int depthI = (int) (depth * 256);
        float depthF = depthI / 256f;
        if (Float.compare(mDepth, depthF) == 0) {
            return;
        }
        dispatchTransactionSurface(depthF);
        mDepth = depthF;
    }

    public void onOverlayScrollChanged(float progress) {
        if (!OVERLAY_SCROLL_ENABLED) {
            return;
        }
        // Add some padding to the progress, such we don't change the depth on the last frames of
        // the animation. It's possible that a user flinging the feed quickly would scroll
        // horizontally by accident, causing the device to enter client composition unnecessarily.
        progress = Math.min(progress * 1.1f, 1f);

        // Round out the progress to dedupe frequent, non-perceptable updates
        int progressI = (int) (progress * 256);
        float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
        if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
            return;
        }
        mOverlayScrollProgress = progressF;
        dispatchTransactionSurface(mDepth);
    }

    private boolean dispatchTransactionSurface(float depth) {
        boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
        if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
            return false;
        }
    @Override
    protected void applyDepthAndBlur() {
        ensureDependencies();
        depth = Math.max(depth, mOverlayScrollProgress);
        IBinder windowToken = mLauncher.getRootView().getWindowToken();
        if (windowToken != null) {
            mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
        }

        if (supportsBlur) {
            boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
            boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;

            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
                    ? 0 : (int) (depth * mMaxBlurRadius);
            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
                    .setBackgroundBlurRadius(mSurface, mCurrentBlur)
                    .setOpaque(mSurface, isSurfaceOpaque);

            // Set early wake-up flags when we know we're executing an expensive operation, this way
            // SurfaceFlinger will adjust its internal offsets to avoid jank.
            boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
            if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
                transaction.setEarlyWakeupStart();
                mInEarlyWakeUp = true;
            } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
                transaction.setEarlyWakeupEnd();
                mInEarlyWakeUp = false;
            }

            AttachedSurfaceControl rootSurfaceControl =
                    mLauncher.getRootView().getRootSurfaceControl();
            if (rootSurfaceControl != null) {
                rootSurfaceControl.applyTransactionOnDraw(transaction);
            }
        }
        return true;
        super.applyDepthAndBlur();
    }

    @Override
@@ -377,7 +197,6 @@ public class DepthController implements StateHandler<LauncherState>,
        writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
        writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
        writer.println(prefix + "\tmSurface=" + mSurface);
        writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
        writer.println(prefix + "\tmDepth=" + mDepth);
        writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
        writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
+153 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.quickstep.util;

import android.app.WallpaperManager;
import android.os.IBinder;
import android.util.FloatProperty;
import android.view.AttachedSurfaceControl;
import android.view.SurfaceControl;

import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.BlurUtils;

/**
 * Utility class for applying depth effect
 */
public class BaseDepthController {

    public static final FloatProperty<BaseDepthController> DEPTH =
            new FloatProperty<BaseDepthController>("depth") {
                @Override
                public void setValue(BaseDepthController depthController, float depth) {
                    depthController.setDepth(depth);
                }

                @Override
                public Float get(BaseDepthController depthController) {
                    return depthController.mDepth;
                }
            };

    protected final Launcher mLauncher;

    /**
     * Blur radius when completely zoomed out, in pixels.
     */
    protected final int mMaxBlurRadius;
    protected final WallpaperManager mWallpaperManager;
    protected boolean mCrossWindowBlursEnabled;

    /**
     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
     */
    protected float mDepth;

    protected SurfaceControl mSurface;

    // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
    // marking the launcher surface as opaque.  Only used in certain Launcher states.
    private boolean mHasContentBehindLauncher;
    /**
     * Last blur value, in pixels, that was applied.
     * For debugging purposes.
     */
    protected int mCurrentBlur;
    /**
     * If we requested early wake-up offsets to SurfaceFlinger.
     */
    protected boolean mInEarlyWakeUp;

    public BaseDepthController(Launcher activity) {
        mLauncher = activity;
        mMaxBlurRadius = activity.getResources().getInteger(R.integer.max_depth_blur_radius);
        mWallpaperManager = activity.getSystemService(WallpaperManager.class);
    }

    protected void setCrossWindowBlursEnabled(boolean isEnabled) {
        mCrossWindowBlursEnabled = isEnabled;
        applyDepthAndBlur();
    }

    public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
        mHasContentBehindLauncher = hasContentBehindLauncher;
    }

    protected void applyDepthAndBlur() {
        float depth = mDepth;
        IBinder windowToken = mLauncher.getRootView().getWindowToken();
        if (windowToken != null) {
            mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
        }

        if (!BlurUtils.supportsBlursOnWindows()) {
            return;
        }
        if (mSurface == null || !mSurface.isValid()) {
            return;
        }
        boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
        boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;

        mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg
                ? 0 : (int) (depth * mMaxBlurRadius);
        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
                .setBackgroundBlurRadius(mSurface, mCurrentBlur)
                .setOpaque(mSurface, isSurfaceOpaque);

        // Set early wake-up flags when we know we're executing an expensive operation, this way
        // SurfaceFlinger will adjust its internal offsets to avoid jank.
        boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
        if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
            transaction.setEarlyWakeupStart();
            mInEarlyWakeUp = true;
        } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
            transaction.setEarlyWakeupEnd();
            mInEarlyWakeUp = false;
        }

        AttachedSurfaceControl rootSurfaceControl =
                mLauncher.getRootView().getRootSurfaceControl();
        if (rootSurfaceControl != null) {
            rootSurfaceControl.applyTransactionOnDraw(transaction);
        }
    }

    protected void setDepth(float depth) {
        depth = Utilities.boundToRange(depth, 0, 1);
        // Round out the depth to dedupe frequent, non-perceptable updates
        int depthI = (int) (depth * 256);
        float depthF = depthI / 256f;
        if (Float.compare(mDepth, depthF) == 0) {
            return;
        }
        mDepth = depthF;
        applyDepthAndBlur();
    }

    /**
     * Sets the specified app target surface to apply the blur to.
     */
    protected void setSurface(SurfaceControl surface) {
        if (mSurface != surface) {
            mSurface = surface;
            applyDepthAndBlur();
        }
    }
}