Merge "Dumping view capture directly to file instead of loading it all in memory" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index e21dcba..c2e8658 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -110,7 +110,7 @@
  */
 public abstract class BaseQuickstepLauncher extends Launcher {
 
-    private DepthController mDepthController = new DepthController(this);
+    private DepthController mDepthController;
     private QuickstepTransitionManager mAppTransitionManager;
 
     /**
@@ -247,7 +247,6 @@
     @Override
     public void onScrollChanged(float progress) {
         super.onScrollChanged(progress);
-        mDepthController.onOverlayScrollChanged(progress);
         onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX);
     }
 
@@ -345,6 +344,7 @@
         mAppTransitionManager.registerRemoteTransitions();
 
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+        mDepthController = new DepthController(this);
     }
 
     private void onTISConnected(TISBinder binder) {
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c089d1b..d251f3e 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -78,6 +78,7 @@
 import android.provider.Settings;
 import android.util.Pair;
 import android.util.Size;
+import android.view.CrossWindowBlurListeners;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
@@ -93,6 +94,7 @@
 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;
@@ -143,8 +145,6 @@
  */
 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);
 
@@ -1044,54 +1044,37 @@
     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))
+                        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:
-                // - Results would be able to be cached as a texture
-                // - There won't be texture allocation overhead, because EffectLayers don't have
-                //   buffers
-                ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
-                SurfaceControl parent = viewRootImpl != null
-                        ? viewRootImpl.getSurfaceControl()
-                        : null;
-                dimLayer = new SurfaceControl.Builder()
-                        .setName("Blur layer")
-                        .setParent(parent)
-                        .setOpaque(false)
-                        .setHidden(false)
-                        .setEffectLayer()
-                        .build();
-            } else {
-                dimLayer = null;
-            }
+            // 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:
+            // - Results would be able to be cached as a texture
+            // - There won't be texture allocation overhead, because EffectLayers don't have
+            //   buffers
+            ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
+            SurfaceControl parent = viewRootImpl != null
+                    ? viewRootImpl.getSurfaceControl()
+                    : null;
+            SurfaceControl dimLayer = new SurfaceControl.Builder()
+                    .setName("Blur layer")
+                    .setParent(parent)
+                    .setOpaque(false)
+                    .setHidden(false)
+                    .setEffectLayer()
+                    .build();
 
-            depthController.setSurface(dimLayer);
-            backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    depthController.setIsInLaunchTransition(true);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    depthController.setIsInLaunchTransition(false);
-                    depthController.setSurface(null);
-                    if (dimLayer != null) {
-                        new SurfaceControl.Transaction()
-                                .remove(dimLayer)
-                                .apply();
-                    }
-                }
-            });
+            backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() ->
+                    new SurfaceControl.Transaction().remove(dimLayer).apply()));
         }
+
         return backgroundRadiusAnim;
     }
 
@@ -1936,4 +1919,17 @@
             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);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index eda0823..1311b1d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -23,13 +23,8 @@
 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 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 @@
 /**
  * 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 @@
         }
     }
 
-    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 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 (mWallpaperManager == null) {
-            mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
-            mWallpaperManager = mLauncher.getSystemService(WallpaperManager.class);
-        }
-
         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,23 +120,13 @@
                     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;
-    }
-
     /**
      * Sets if the underlying activity is started or not
      */
@@ -219,26 +139,6 @@
         }
     }
 
-    /**
-     * 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 @@
         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 @@
         }
     }
 
-    /**
-     * 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 @@
         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);
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index b01168d..55c3c20 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -676,9 +676,11 @@
         updateButtonLayoutSpacing();
     }
 
-    /** Adds the correct spacing to 3 button nav container. No-op if using gesture nav */
+    /**
+     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav or kids mode.
+     */
     private void updateButtonLayoutSpacing() {
-        if (!mContext.isThreeButtonNav()) {
+        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()) {
             return;
         }
         DeviceProfile dp = mContext.getDeviceProfile();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index ea15acb..dbf9759 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -314,10 +314,10 @@
                 int qsbEnd;
                 if (layoutRtl) {
                     qsbStart = iconEnd + mItemMarginLeftRight;
-                    qsbEnd = qsbStart + deviceProfile.qsbWidth;
+                    qsbEnd = qsbStart + deviceProfile.hotseatQsbWidth;
                 } else {
                     qsbEnd = iconEnd - mItemMarginLeftRight;
-                    qsbStart = qsbEnd - deviceProfile.qsbWidth;
+                    qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth;
                 }
                 int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
                 int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0f6de73..929dc20 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -295,10 +295,11 @@
                 boolean isRtl = Utilities.isRtl(child.getResources());
                 float hotseatIconCenter = isRtl
                         ? launcherDp.widthPx - hotseatPadding.right + borderSpacing
-                        + launcherDp.qsbWidth / 2f
-                        : hotseatPadding.left - borderSpacing - launcherDp.qsbWidth / 2f;
+                        + launcherDp.hotseatQsbWidth / 2f
+                        : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
                 float childCenter = (child.getLeft() + child.getRight()) / 2f;
-                float halfQsbIconWidthDiff = (launcherDp.qsbWidth - taskbarDp.iconSizePx) / 2f;
+                float halfQsbIconWidthDiff =
+                        (launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f;
                 setter.addFloat(child, ICON_TRANSLATE_X,
                         isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
                         hotseatIconCenter - childCenter, LINEAR);
@@ -312,7 +313,7 @@
                                 : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
                 setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
 
-                float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.qsbWidth;
+                float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.hotseatQsbWidth;
                 if (child instanceof  HorizontalInsettableView) {
                     setter.addFloat((HorizontalInsettableView) child,
                             HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0,
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
new file mode 100644
index 0000000..4030630
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -0,0 +1,153 @@
+/*
+ * 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();
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
index 1208a2a..b50d3ee 100644
--- a/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
@@ -110,7 +110,7 @@
                 "\tnumShownHotseatIcons: 4\n" +
                 "\thotseatBorderSpace: 95.0px (36.190475dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 913.0px (347.8095dp)\n" +
+                "\thotseatQsbWidth: 913.0px (347.8095dp)\n" +
                 "\tisTaskbarPresent:false\n" +
                 "\tisTaskbarPresentInApps:false\n" +
                 "\ttaskbarSize: 0.0px (0.0dp)\n" +
@@ -229,7 +229,7 @@
                 "\tnumShownHotseatIcons: 4\n" +
                 "\thotseatBorderSpace: 95.0px (36.190475dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 913.0px (347.8095dp)\n" +
+                "\thotseatQsbWidth: 913.0px (347.8095dp)\n" +
                 "\tisTaskbarPresent:false\n" +
                 "\tisTaskbarPresentInApps:false\n" +
                 "\ttaskbarSize: 0.0px (0.0dp)\n" +
@@ -349,7 +349,7 @@
                 "\tnumShownHotseatIcons: 5\n" +
                 "\thotseatBorderSpace: 101.0px (50.5dp)\n" +
                 "\tisQsbInline: true\n" +
-                "\tqsbWidth: 855.0px (427.5dp)\n" +
+                "\thotseatQsbWidth: 855.0px (427.5dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 120.0px (60.0dp)\n" +
@@ -469,7 +469,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 100.0px (50.0dp)\n" +
                 "\tisQsbInline: true\n" +
-                "\tqsbWidth: 640.0px (320.0dp)\n" +
+                "\thotseatQsbWidth: 640.0px (320.0dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 120.0px (60.0dp)\n" +
@@ -560,8 +560,8 @@
                 "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
                 "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
                 "\tbottomSheetTopPadding: 704.0px (352.0dp)\n" +
-                "\tallAppsShiftRange: 1856.0px (928.0dp)\n" +
-                "\tallAppsTopPadding: 704.0px (352.0dp)\n" +
+                "\tallAppsShiftRange: 1936.0px (968.0dp)\n" +
+                "\tallAppsTopPadding: 624.0px (312.0dp)\n" +
                 "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                 "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
                 "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
@@ -589,7 +589,7 @@
                 "\tnumShownHotseatIcons: 5\n" +
                 "\thotseatBorderSpace: 116.0px (58.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1300.0px (650.0dp)\n" +
+                "\thotseatQsbWidth: 1300.0px (650.0dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 120.0px (60.0dp)\n" +
@@ -680,8 +680,8 @@
                 "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
                 "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
                 "\tbottomSheetTopPadding: 704.0px (352.0dp)\n" +
-                "\tallAppsShiftRange: 1856.0px (928.0dp)\n" +
-                "\tallAppsTopPadding: 704.0px (352.0dp)\n" +
+                "\tallAppsShiftRange: 1936.0px (968.0dp)\n" +
+                "\tallAppsTopPadding: 624.0px (312.0dp)\n" +
                 "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                 "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
                 "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
@@ -709,7 +709,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 116.0px (58.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1300.0px (650.0dp)\n" +
+                "\thotseatQsbWidth: 1300.0px (650.0dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 120.0px (60.0dp)\n" +
@@ -829,7 +829,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 61.0px (23.238094dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1467.0px (558.8571dp)\n" +
+                "\thotseatQsbWidth: 1467.0px (558.8571dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 158.0px (60.190475dp)\n" +
@@ -949,7 +949,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 105.0px (40.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1467.0px (558.8571dp)\n" +
+                "\thotseatQsbWidth: 1467.0px (558.8571dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 158.0px (60.190475dp)\n" +
@@ -1069,7 +1069,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 47.0px (17.904762dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1236.0px (470.85715dp)\n" +
+                "\thotseatQsbWidth: 1236.0px (470.85715dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 158.0px (60.190475dp)\n" +
@@ -1189,7 +1189,7 @@
                 "\tnumShownHotseatIcons: 6\n" +
                 "\thotseatBorderSpace: 84.0px (32.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1236.0px (470.85715dp)\n" +
+                "\thotseatQsbWidth: 1236.0px (470.85715dp)\n" +
                 "\tisTaskbarPresent:true\n" +
                 "\tisTaskbarPresentInApps:true\n" +
                 "\ttaskbarSize: 158.0px (60.190475dp)\n" +
@@ -1308,7 +1308,7 @@
                 "\tnumShownHotseatIcons: 4\n" +
                 "\thotseatBorderSpace: 0.0px (0.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1525.0px (580.9524dp)\n" +
+                "\thotseatQsbWidth: 1525.0px (580.9524dp)\n" +
                 "\tisTaskbarPresent:false\n" +
                 "\tisTaskbarPresentInApps:false\n" +
                 "\ttaskbarSize: 0.0px (0.0dp)\n" +
@@ -1427,7 +1427,7 @@
                 "\tnumShownHotseatIcons: 4\n" +
                 "\thotseatBorderSpace: 0.0px (0.0dp)\n" +
                 "\tisQsbInline: false\n" +
-                "\tqsbWidth: 1621.0px (617.5238dp)\n" +
+                "\thotseatQsbWidth: 1621.0px (617.5238dp)\n" +
                 "\tisTaskbarPresent:false\n" +
                 "\tisTaskbarPresentInApps:false\n" +
                 "\ttaskbarSize: 0.0px (0.0dp)\n" +
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1f18217..0d41230 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -41,7 +41,6 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.uioverrides.ApiWrapper;
@@ -56,6 +55,8 @@
 public class DeviceProfile {
 
     private static final int DEFAULT_DOT_SIZE = 100;
+    private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
+
     // Ratio of empty space, qsb should take up to appear visually centered.
     private final float mQsbCenterFactor;
 
@@ -169,13 +170,12 @@
     // Start is the side next to the nav bar, end is the side next to the workspace
     public final int hotseatBarSidePaddingStartPx;
     public final int hotseatBarSidePaddingEndPx;
+    public int hotseatQsbWidth; // only used when isQsbInline
     public final int hotseatQsbHeight;
     public final int hotseatQsbVisualHeight;
     private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
 
-    public int qsbWidth; // only used when isQsbInline
-
     // All apps
     public Point allAppsBorderSpacePx;
     public int allAppsShiftRange;
@@ -311,10 +311,6 @@
                 + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding)
                 + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding
 
-        allAppsTopPadding = isTablet ? bottomSheetTopPadding : 0;
-        allAppsShiftRange = isTablet
-                ? heightPx - allAppsTopPadding
-                : res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
         folderContentPaddingLeftRight =
                 res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
@@ -465,7 +461,21 @@
 
         // Hotseat and QSB width depends on updated cellSize and workspace padding
         hotseatBorderSpace = calculateHotseatBorderSpace();
-        qsbWidth = calculateQsbWidth();
+        hotseatQsbWidth = calculateQsbWidth();
+
+        // AllApps height calculation depends on updated cellSize
+        if (isTablet) {
+            int collapseHandleHeight =
+                    res.getDimensionPixelOffset(R.dimen.bottom_sheet_handle_area_height);
+            int contentHeight = heightPx - collapseHandleHeight - hotseatQsbHeight;
+            int targetContentHeight = (int) (allAppsCellHeightPx * ALL_APPS_TABLET_MAX_ROWS);
+            allAppsTopPadding = Math.max(mInsets.top, contentHeight - targetContentHeight);
+            allAppsShiftRange = heightPx - allAppsTopPadding;
+        } else {
+            allAppsTopPadding = 0;
+            allAppsShiftRange =
+                    res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
+        }
 
         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
                 R.dimen.drag_flingToDeleteMinVelocity);
@@ -1068,7 +1078,7 @@
                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
 
             // Push icons to the side
-            int additionalQsbSpace = isQsbInline ? qsbWidth + hotseatBorderSpace : 0;
+            int additionalQsbSpace = isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
             int requiredWidth = iconSizePx * numShownHotseatIcons
                     + hotseatBorderSpace * (numShownHotseatIcons - 1)
                     + additionalQsbSpace;
@@ -1093,7 +1103,7 @@
                 hotseatBarPadding.right += diff;
             }
         } else if (isScalableGrid) {
-            int sideSpacing = (availableWidthPx - qsbWidth) / 2;
+            int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
             hotseatBarPadding.set(sideSpacing,
                     0,
                     sideSpacing,
@@ -1366,7 +1376,7 @@
         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
-        writer.println(prefix + pxToDpStr("qsbWidth", qsbWidth));
+        writer.println(prefix + pxToDpStr("hotseatQsbWidth", hotseatQsbWidth));
 
         writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
         writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 05ed319..bf492a9 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -47,7 +47,6 @@
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
     private final View mQsb;
-    private final int mQsbHeight;
 
     public Hotseat(Context context) {
         this(context, null);
@@ -62,8 +61,6 @@
 
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
         addView(mQsb);
-
-        mQsbHeight = getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
     }
 
     /**
@@ -171,29 +168,29 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        int qsbWidth = mActivity.getDeviceProfile().qsbWidth;
-
-        mQsb.measure(MeasureSpec.makeMeasureSpec(qsbWidth, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        mQsb.measure(MeasureSpec.makeMeasureSpec(dp.hotseatQsbWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(dp.hotseatQsbHeight, MeasureSpec.EXACTLY));
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        int qsbWidth = mQsb.getMeasuredWidth();
+        int qsbMeasuredWidth = mQsb.getMeasuredWidth();
         int left;
-        if (mActivity.getDeviceProfile().isQsbInline) {
-            int qsbSpace = mActivity.getDeviceProfile().hotseatBorderSpace;
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        if (dp.isQsbInline) {
+            int qsbSpace = dp.hotseatBorderSpace;
             left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
-                    : l + getPaddingLeft() - qsbWidth - qsbSpace;
+                    : l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
         } else {
-            left = (r - l - qsbWidth) / 2;
+            left = (r - l - qsbMeasuredWidth) / 2;
         }
-        int right = left + qsbWidth;
+        int right = left + qsbMeasuredWidth;
 
-        int bottom = b - t - mActivity.getDeviceProfile().getQsbOffsetY();
-        int top = bottom - mQsbHeight;
+        int bottom = b - t - dp.getQsbOffsetY();
+        int top = bottom - dp.hotseatQsbHeight;
         mQsb.layout(left, top, right, bottom);
     }
 
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index aee7c4c..fedc91f 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -91,12 +91,12 @@
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
         if (lp != null) {
             int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
+            DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
             if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
                 bottomMargin <<= 1;  // Double margin to add space above search bar.
-                bottomMargin += getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
+                bottomMargin += dp.hotseatQsbHeight;
             }
 
-            DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
             if (!dp.isGestureMode) {
                 if (dp.isTaskbarPresent) {
                     bottomMargin += dp.taskbarSize;
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 3ca8ba2..b63715c 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -58,7 +58,7 @@
         float myCenter = ws.getTop() + halfHeight;
         float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
         float actualCellTop = myCenter - cellTopFromCenter * scale;
-        return new ScaleAndTranslation(scale, 0, (shrunkTop - actualCellTop) / scale);
+        return new ScaleAndTranslation(scale, 0, shrunkTop - actualCellTop);
     }
 
     @Override