package com.android.launcher3; import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE; 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 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) { // Update device profile before notifying the children. mActivity.getDeviceProfile().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. * * @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.isLandscape) { boolean isGesturalMode = ResourceUtils.getIntegerByName( "config_navBarInteractionMode", resources, INVALID_RESOURCE_HANDLE) == 2; if (dp.isTablet || isGesturalMode) { 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); } }