Files
Lawnchair/src/com/android/launcher3/LauncherRootView.java
T
Tony Wickham 7eb5b53865 Keep insets stable when taskbar is destroyed/recreated
- Calculate nav bar insets ourselves. Currently when taskbar is going to be present, we use taskbarSize as the nav bar insets. This is consistent with other existing calculations, but going forward we should instead always use the nav bar size instead of taskbar size, given we don't want taskbar to inset launcher (since taskbar is hidden).
- Also update tappable insets to be 0 in gesture mode. Test: Swipe to all apps, ensure there's no background protection at the bottom.

Test: Rotate device, no visual jumps
Test: Stash taskbar, quick switch a couple times without settling, and swipe up to overview; no jank due to reapplyState()

Bug: 198798034
Fixes: 197232424
Fixes: 197212581
Change-Id: I4c2bb5816dbb214846bd9f2a46c6f759c0545911
2021-09-21 13:52:36 -07:00

215 lines
7.9 KiB
Java

package com.android.launcher3;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewDebug;
import android.view.WindowInsets;
import androidx.annotation.RequiresApi;
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.ApiWrapper;
import java.util.Collections;
import java.util.List;
public class LauncherRootView extends InsettableFrameLayout {
private final Rect mTempRect = new Rect();
private final StatefulActivity mActivity;
@ViewDebug.ExportedProperty(category = "launcher")
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
private WindowStateListener mWindowStateListener;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mDisallowBackGesture;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mForceHideBackArrow;
private final SysUiScrim mSysUiScrim;
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = StatefulActivity.fromContext(context);
mSysUiScrim = new SysUiScrim(this);
}
private void handleSystemWindowInsets(Rect insets) {
DeviceProfile dp = mActivity.getDeviceProfile();
// Taskbar provides insets, but we don't want that for most Launcher elements so remove it.
mTempRect.set(insets);
insets = mTempRect;
insets.bottom = Math.max(0, insets.bottom - dp.nonOverlappingTaskbarInset);
// Update device profile before notifying the children.
dp.updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
if (resetState) {
mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (Utilities.ATLEAST_R) {
insets = updateInsetsDueToTaskbar(insets);
Insets systemWindowInsets = insets.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
mTempRect.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right,
systemWindowInsets.bottom);
} else {
mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
handleSystemWindowInsets(mTempRect);
return insets;
}
/**
* Taskbar provides nav bar and tappable insets. However, taskbar is not attached immediately,
* and can be destroyed and recreated. Thus, instead of relying on taskbar being present to
* get its insets, we calculate them ourselves so they are stable regardless of whether taskbar
* is currently attached.
*
* TODO(b/198798034): Currently we always calculate nav insets as taskbarSize, but then we
* subtract nonOverlappingTaskbarInset in handleSystemWindowInsets(). Instead, we should just
* calculate the normal nav bar height here, and remove nonOverlappingTaskbarInset altogether.
*
* @param oldInsets The system-provided insets, which we are modifying.
* @return The updated insets.
*/
@RequiresApi(api = Build.VERSION_CODES.R)
private WindowInsets updateInsetsDueToTaskbar(WindowInsets oldInsets) {
if (!ApiWrapper.TASKBAR_DRAWN_IN_PROCESS) {
// 3P launchers based on Launcher3 should still be inset like normal.
return oldInsets;
}
WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
DeviceProfile dp = mActivity.getDeviceProfile();
Resources resources = getResources();
Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
Rect newNavInsets = new Rect(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
oldNavInsets.bottom);
if (dp.isTaskbarPresent) {
// TODO (see javadoc): Remove this block and fall into the next one instead.
newNavInsets.bottom = dp.taskbarSize;
} else if (dp.isLandscape) {
if (dp.isTablet) {
newNavInsets.bottom = ResourceUtils.getNavbarSize(
"navigation_bar_height_landscape", resources);
} else {
int navWidth = ResourceUtils.getNavbarSize("navigation_bar_width", resources);
if (dp.isSeascape()) {
newNavInsets.left = navWidth;
} else {
newNavInsets.right = navWidth;
}
}
} else {
newNavInsets.bottom = ResourceUtils.getNavbarSize("navigation_bar_height", resources);
}
updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(newNavInsets));
updatedInsetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(),
Insets.of(newNavInsets));
mActivity.updateWindowInsets(updatedInsetsBuilder, oldInsets);
return updatedInsetsBuilder.build();
}
@Override
public void setInsets(Rect insets) {
// If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
// modifying child layout params.
if (!insets.equals(mInsets)) {
super.setInsets(insets);
mSysUiScrim.onInsetsChanged(insets);
}
}
public void dispatchInsets() {
mActivity.getDeviceProfile().updateInsets(mInsets);
super.setInsets(mInsets);
}
public void setWindowStateListener(WindowStateListener listener) {
mWindowStateListener = listener;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (mWindowStateListener != null) {
mWindowStateListener.onWindowFocusChanged(hasWindowFocus);
}
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (mWindowStateListener != null) {
mWindowStateListener.onWindowVisibilityChanged(visibility);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
mSysUiScrim.draw(canvas);
super.dispatchDraw(canvas);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b);
setDisallowBackGesture(mDisallowBackGesture);
mSysUiScrim.setSize(r - l, b - t);
}
@TargetApi(Build.VERSION_CODES.Q)
public void setForceHideBackArrow(boolean forceHideBackArrow) {
this.mForceHideBackArrow = forceHideBackArrow;
setDisallowBackGesture(mDisallowBackGesture);
}
@TargetApi(Build.VERSION_CODES.Q)
public void setDisallowBackGesture(boolean disallowBackGesture) {
if (!Utilities.ATLEAST_Q || SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
mDisallowBackGesture = disallowBackGesture;
setSystemGestureExclusionRects((mForceHideBackArrow || mDisallowBackGesture)
? SYSTEM_GESTURE_EXCLUSION_RECT
: Collections.emptyList());
}
public SysUiScrim getSysUiScrim() {
return mSysUiScrim;
}
public interface WindowStateListener {
void onWindowFocusChanged(boolean hasFocus);
void onWindowVisibilityChanged(int visibility);
}
}