diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 3a4bb1086f..3b4a28b39c 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -23,8 +23,8 @@ com.android.quickstep.logging.StatsLogCompatManager - com.android.quickstep.QuickstepTestInformationHandler + com.android.quickstep.util.SystemWindowManagerProxy diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 8fb085dd16..d4969834fc 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -26,7 +26,6 @@ import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; -import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON; import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS; import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; @@ -38,7 +37,6 @@ import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.graphics.Insets; import android.hardware.SensorManager; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; @@ -46,7 +44,6 @@ import android.os.CancellationSignal; import android.os.IBinder; import android.view.Display; import android.view.View; -import android.view.WindowInsets; import android.window.SplashScreen; import androidx.annotation.Nullable; @@ -614,17 +611,4 @@ public abstract class BaseQuickstepLauncher extends Launcher { mDepthController.dump(prefix, writer); } } - - @Override - public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder, - WindowInsets oldInsets) { - // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar - // would count towards it). This is used for the bottom protection in All Apps for example. - if (DisplayController.getNavigationMode(this) == NO_BUTTON) { - Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement()); - Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top, - oldTappableInsets.right, 0); - updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets); - } - } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 9240fb86a7..2f8e4d9dde 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -20,7 +20,6 @@ import android.app.Person; import android.content.Context; import android.content.pm.ShortcutInfo; import android.content.res.Resources; -import android.view.Display; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -36,20 +35,6 @@ public class ApiWrapper { return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; } - /** - * Returns true if the display is an internal displays - */ - public static boolean isInternalDisplay(Display display) { - return display.getType() == Display.TYPE_INTERNAL; - } - - /** - * Returns a unique ID representing the display - */ - public static String getUniqueId(Display display) { - return display.getUniqueId(); - } - /** * Returns the minimum space that should be left empty at the end of hotseat */ diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index e5c7b0e46b..895cf89382 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -35,11 +35,11 @@ import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.window.CachedDisplayInfo; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * Maintains state for supporting nav bars and tracking their gestures in multiple orientations. @@ -51,55 +51,17 @@ import java.util.Objects; */ class OrientationTouchTransformer { - private static class CurrentDisplay { - public Point size; - public int rotation; - - CurrentDisplay() { - this.size = new Point(0, 0); - this.rotation = 0; - } - - CurrentDisplay(Point size, int rotation) { - this.size = size; - this.rotation = rotation; - } - - @Override - public String toString() { - return "CurrentDisplay:" - + " rotation: " + rotation - + " size: " + size; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - CurrentDisplay display = (CurrentDisplay) o; - if (rotation != display.rotation) return false; - - return Objects.equals(size, display.size); - } - - @Override - public int hashCode() { - return Objects.hash(size, rotation); - } - }; - private static final String TAG = "OrientationTouchTransformer"; private static final boolean DEBUG = false; private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1; - private final Map mSwipeTouchRegions = - new HashMap(); + private final Map mSwipeTouchRegions = + new HashMap(); private final RectF mAssistantLeftRegion = new RectF(); private final RectF mAssistantRightRegion = new RectF(); private final RectF mOneHandedModeRegion = new RectF(); - private CurrentDisplay mCurrentDisplay = new CurrentDisplay(); + private CachedDisplayInfo mCachedDisplayInfo = new CachedDisplayInfo(); private int mNavBarGesturalHeight; private final int mNavBarLargerGesturalHeight; private boolean mEnableMultipleRegions; @@ -184,22 +146,22 @@ class OrientationTouchTransformer { * @see #enableMultipleRegions(boolean, Info) */ void createOrAddTouchRegion(Info info) { - mCurrentDisplay = new CurrentDisplay(info.currentSize, info.rotation); + mCachedDisplayInfo = new CachedDisplayInfo(info.currentSize, info.rotation); if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED - && mCurrentDisplay.rotation == mQuickStepStartingRotation) { + && mCachedDisplayInfo.rotation == mQuickStepStartingRotation) { // User already was swiping and the current screen is same rotation as the starting one // Remove active nav bars in other rotations except for the one we started out in resetSwipeRegions(info); return; } - OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplay); + OrientationRectF region = mSwipeTouchRegions.get(mCachedDisplayInfo); if (region != null) { return; } if (mEnableMultipleRegions) { - mSwipeTouchRegions.put(mCurrentDisplay, createRegionForDisplay(info)); + mSwipeTouchRegions.put(mCachedDisplayInfo, createRegionForDisplay(info)); } else { resetSwipeRegions(info); } @@ -245,31 +207,31 @@ class OrientationTouchTransformer { */ private void resetSwipeRegions(Info region) { if (DEBUG) { - Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplay.rotation); + Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation); } - mCurrentDisplay = new CurrentDisplay(region.currentSize, region.rotation); - OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay); + mCachedDisplayInfo = new CachedDisplayInfo(region.currentSize, region.rotation); + OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo); if (regionToKeep == null) { regionToKeep = createRegionForDisplay(region); } mSwipeTouchRegions.clear(); - mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep); + mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep); updateAssistantRegions(regionToKeep); } private void resetSwipeRegions() { - OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay); + OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo); mSwipeTouchRegions.clear(); if (regionToKeep != null) { - mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep); + mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep); updateAssistantRegions(regionToKeep); } } private OrientationRectF createRegionForDisplay(Info display) { if (DEBUG) { - Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation + Log.d(TAG, "creating rotation region for: " + mCachedDisplayInfo.rotation + " with mode: " + mMode + " displayRotation: " + display.rotation); } @@ -368,7 +330,7 @@ class OrientationTouchTransformer { true); } } else { - mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, + mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation, true); } break; @@ -387,7 +349,7 @@ class OrientationTouchTransformer { true); } } else { - mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, + mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation, true); } mLastRectTouched = null; @@ -403,11 +365,12 @@ class OrientationTouchTransformer { if (rect == null) { continue; } - if (rect.applyTransformFromRotation(event, mCurrentDisplay.rotation, false)) { + if (rect.applyTransformFromRotation( + event, mCachedDisplayInfo.rotation, false)) { mLastRectTouched = rect; mActiveTouchRotation = rect.getRotation(); if (mEnableMultipleRegions - && mCurrentDisplay.rotation == mActiveTouchRotation) { + && mCachedDisplayInfo.rotation == mActiveTouchRotation) { // TODO(b/154580671) might make this block unnecessary // Start a touch session for the default nav region for the display mQuickStepStartingRotation = mLastRectTouched.getRotation(); @@ -430,7 +393,7 @@ class OrientationTouchTransformer { pw.println(" lastTouchedRegion=" + mLastRectTouched); pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions); StringBuilder regions = new StringBuilder(" currentTouchableRotations="); - for (CurrentDisplay key: mSwipeTouchRegions.keySet()) { + for (CachedDisplayInfo key: mSwipeTouchRegions.keySet()) { OrientationRectF rectF = mSwipeTouchRegions.get(key); regions.append(rectF).append(" "); } diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index 4f5c368f31..0bba47d9e3 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -599,7 +599,7 @@ public class RecentsOrientedState implements width = Math.min(currentSize.x, currentSize.y); height = Math.max(currentSize.x, currentSize.y); } - return idp.getBestMatch(width, height); + return idp.getBestMatch(width, height, mRecentsActivityRotation); } private static String nameAndAddress(Object obj) { diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java new file mode 100644 index 0000000000..19a48dbf20 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java @@ -0,0 +1,46 @@ +/* + * 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.content.Context; +import android.view.Display; + +import com.android.launcher3.util.window.WindowManagerProxy; + +/** + * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher + */ +public class SystemWindowManagerProxy extends WindowManagerProxy { + + public SystemWindowManagerProxy(Context context) { + super(true); + } + + @Override + protected String getDisplayId(Display display) { + return display.getUniqueId(); + } + + @Override + public boolean isInternalDisplay(Display display) { + return display.getType() == Display.TYPE_INTERNAL; + } + + @Override + public int getRotation(Context context) { + return context.getResources().getConfiguration().windowConfiguration.getRotation(); + } +} diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java index 6dc9f8d921..c6cdafc599 100644 --- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java +++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java @@ -15,16 +15,15 @@ */ package com.android.quickstep.util; -import static android.view.Display.DEFAULT_DISPLAY; - import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; +import android.util.ArrayMap; +import android.util.Pair; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -38,6 +37,10 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.util.ReflectionHelpers; +import com.android.launcher3.util.RotationUtils; +import com.android.launcher3.util.WindowBounds; +import com.android.launcher3.util.window.CachedDisplayInfo; +import com.android.launcher3.util.window.WindowManagerProxy; import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.SystemUiProxy; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -142,29 +145,34 @@ public class TaskViewSimulatorTest { LauncherModelHelper helper = new LauncherModelHelper(); try { helper.sandboxContext.allow(SystemUiProxy.INSTANCE); + int rotation = mDisplaySize.x > mDisplaySize.y + ? Surface.ROTATION_90 : Surface.ROTATION_0; + CachedDisplayInfo cdi = + new CachedDisplayInfo("test-display", mDisplaySize, rotation , new Rect()); + WindowBounds wm = new WindowBounds( + new Rect(0, 0, mDisplaySize.x, mDisplaySize.y), + mDisplayInsets); + WindowBounds[] allBounds = new WindowBounds[4]; + for (int i = 0; i < 4; i++) { + Rect boundsR = new Rect(wm.bounds); + Rect insetsR = new Rect(wm.insets); - Display display = mock(Display.class); - doReturn(DEFAULT_DISPLAY).when(display).getDisplayId(); - doReturn(mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0) - .when(display).getRotation(); - doAnswer(i -> { - ((Point) i.getArgument(0)).set(mDisplaySize.x, mDisplaySize.y); - return null; - }).when(display).getRealSize(any()); - doAnswer(i -> { - Point smallestSize = i.getArgument(0); - Point largestSize = i.getArgument(1); - smallestSize.x = smallestSize.y = Math.min(mDisplaySize.x, mDisplaySize.y); - largestSize.x = largestSize.y = Math.max(mDisplaySize.x, mDisplaySize.y); + RotationUtils.rotateRect(insetsR, RotationUtils.deltaRotation(rotation, i)); + RotationUtils.rotateRect(boundsR, RotationUtils.deltaRotation(rotation, i)); + boundsR.set(0, 0, Math.abs(boundsR.width()), Math.abs(boundsR.height())); + allBounds[i] = new WindowBounds(boundsR, insetsR); + } - smallestSize.x -= mDisplayInsets.left + mDisplayInsets.right; - largestSize.x -= mDisplayInsets.left + mDisplayInsets.right; + WindowManagerProxy wmProxy = mock(WindowManagerProxy.class); + doReturn(cdi).when(wmProxy).getDisplayInfo(any()); + doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any()); - smallestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom; - largestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom; - return null; - }).when(display).getCurrentSizeRange(any(), any()); - DisplayController.Info mockInfo = new Info(helper.sandboxContext, display); + ArrayMap> perDisplayBoundsCache = + new ArrayMap<>(); + perDisplayBoundsCache.put(cdi.id, Pair.create(cdi.normalize(), allBounds)); + + DisplayController.Info mockInfo = new Info( + helper.sandboxContext, mock(Display.class), wmProxy, perDisplayBoundsCache); DisplayController controller = DisplayController.INSTANCE.get(helper.sandboxContext); @@ -172,7 +180,7 @@ public class TaskViewSimulatorTest { ReflectionHelpers.setField(controller, "mInfo", mockInfo); mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext) - .getBestMatch(mAppBounds.width(), mAppBounds.height()); + .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation); mDeviceProfile.updateInsets(mLauncherInsets); TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext, diff --git a/res/values/config.xml b/res/values/config.xml index 1c996eb390..509f3636fe 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,6 +69,7 @@ + diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index b47554f744..f53d01e183 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -29,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.res.Configuration; -import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -41,8 +40,6 @@ import android.util.Log; import android.view.ActionMode; import android.view.Display; import android.view.View; -import android.view.WindowInsets.Type; -import android.view.WindowMetrics; import android.widget.Toast; import androidx.annotation.NonNull; @@ -322,11 +319,7 @@ public abstract class BaseDraggingActivity extends BaseActivity protected WindowBounds getMultiWindowDisplaySize() { if (Utilities.ATLEAST_R) { - WindowMetrics wm = getWindowManager().getCurrentWindowMetrics(); - - Insets insets = wm.getWindowInsets().getInsets(Type.systemBars()); - return new WindowBounds(wm.getBounds(), - new Rect(insets.left, insets.top, insets.right, insets.bottom)); + return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics()); } // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return // the app window size diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index cd9bbf70df..41157f5620 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -75,6 +75,7 @@ public class DeviceProfile { public final int heightPx; public final int availableWidthPx; public final int availableHeightPx; + public final int rotationHint; public final float aspectRatio; @@ -239,6 +240,7 @@ public class DeviceProfile { this.isGestureMode = isGestureMode; windowX = windowBounds.bounds.left; windowY = windowBounds.bounds.top; + this.rotationHint = windowBounds.rotationHint; isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode; @@ -543,8 +545,8 @@ public class DeviceProfile { } public Builder toBuilder(Context context) { - WindowBounds bounds = - new WindowBounds(widthPx, heightPx, availableWidthPx, availableHeightPx); + WindowBounds bounds = new WindowBounds( + widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint); bounds.bounds.offsetTo(windowX, windowY); return new Builder(context, inv, mInfo) .setWindowBounds(bounds) diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 886c6573d7..25fd643b11 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -55,6 +55,7 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Themes; import com.android.launcher3.util.WindowBounds; +import com.android.launcher3.util.window.WindowManagerProxy; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -180,8 +181,7 @@ public class InvariantDeviceProfile { private final ArrayList mChangeListeners = new ArrayList<>(); @VisibleForTesting - public InvariantDeviceProfile() { - } + public InvariantDeviceProfile() { } @TargetApi(23) private InvariantDeviceProfile(Context context) { @@ -278,11 +278,16 @@ public class InvariantDeviceProfile { } private static @DeviceType int getDeviceType(Info displayInfo) { - // Each screen has two profiles (portrait/landscape), so devices with four or more - // supported profiles implies two or more internal displays. - if (displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get()) { + int flagPhone = 1 << 0; + int flagTablet = 1 << 1; + + int type = displayInfo.supportedBounds.stream() + .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone) + .reduce(0, (a, b) -> a | b); + if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) { + // device has profiles supporting both phone and table modes return TYPE_MULTI_DISPLAY; - } else if (displayInfo.supportedBounds.stream().allMatch(displayInfo::isTablet)) { + } else if (type == flagTablet) { return TYPE_TABLET; } else { return TYPE_PHONE; @@ -613,10 +618,14 @@ public class InvariantDeviceProfile { float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density; float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density; - return getBestMatch(screenWidth, screenHeight); + return getBestMatch(screenWidth, screenHeight, + WindowManagerProxy.INSTANCE.get(context).getRotation(context)); } - public DeviceProfile getBestMatch(float screenWidth, float screenHeight) { + /** + * Returns the device profile matching the provided screen configuration + */ + public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) { DeviceProfile bestMatch = supportedProfiles.get(0); float minDiff = Float.MAX_VALUE; @@ -626,6 +635,8 @@ public class InvariantDeviceProfile { if (diff < minDiff) { minDiff = diff; bestMatch = profile; + } else if (diff == minDiff && profile.rotationHint == rotation) { + bestMatch = profile; } } return bestMatch; diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java index e3aa758eae..a5c5c02735 100644 --- a/src/com/android/launcher3/LauncherRootView.java +++ b/src/com/android/launcher3/LauncherRootView.java @@ -1,24 +1,19 @@ 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 com.android.launcher3.util.window.WindowManagerProxy; import java.util.Collections; import java.util.List; @@ -60,76 +55,12 @@ public class LauncherRootView extends InsettableFrameLayout { @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()); - } + insets = WindowManagerProxy.INSTANCE.get(getContext()) + .normalizeWindowInsets(getContext(), insets, mTempRect); 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 = dp.isTaskbarPresent - ? 0 - : 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 = dp.isTaskbarPresent - ? 0 - : 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 diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java index ece123dc62..1c36db1048 100644 --- a/src/com/android/launcher3/ResourceUtils.java +++ b/src/com/android/launcher3/ResourceUtils.java @@ -28,6 +28,9 @@ public class ResourceUtils { public static final String NAVBAR_BOTTOM_GESTURE_LARGER_SIZE = "navigation_bar_gesture_larger_height"; + public static final String NAVBAR_HEIGHT = "navigation_bar_height"; + public static final String NAVBAR_HEIGHT_LANDSCAPE = "navigation_bar_height_landscape"; + public static int getNavbarSize(String resName, Resources res) { return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE); } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 2459e0977a..cc170643c9 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -87,6 +87,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; +import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; @@ -128,7 +129,8 @@ public class LauncherPreviewRenderer extends ContextWrapper public PreviewContext(Context base, InvariantDeviceProfile idp) { super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, - CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE); + CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE, + WindowManagerProxy.INSTANCE); mIdp = idp; mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); mObjectMap.put(LauncherAppState.INSTANCE, diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java index b45c97b6e2..c554d069f3 100644 --- a/src/com/android/launcher3/statemanager/StatefulActivity.java +++ b/src/com/android/launcher3/statemanager/StatefulActivity.java @@ -17,15 +17,11 @@ package com.android.launcher3.statemanager; import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE; -import android.graphics.Insets; -import android.os.Build; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; -import android.view.WindowInsets; import androidx.annotation.CallSuper; -import androidx.annotation.RequiresApi; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.LauncherRootView; @@ -178,14 +174,6 @@ public abstract class StatefulActivity> Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); } - /** - * Gives subclasses a chance to override some window insets (via - * {@link android.view.WindowInsets.Builder#setInsets(int, Insets)}). - */ - @RequiresApi(api = Build.VERSION_CODES.R) - public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder, - WindowInsets oldInsets) { } - /** * Runs the given {@param r} runnable when this activity binds to the touch interaction service. */ diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 867fd990d0..8b425daa5e 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -21,7 +21,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import static com.android.launcher3.Utilities.dpiFromPx; -import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH; +import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index f944d3c2e3..22e3de8f76 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -26,10 +26,8 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter; -import static com.android.launcher3.util.WindowManagerCompat.MIN_LARGE_TABLET_WIDTH; -import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH; - -import static java.util.Collections.emptyMap; +import static com.android.launcher3.util.window.WindowManagerProxy.MIN_LARGE_TABLET_WIDTH; +import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -38,12 +36,13 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Build; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.view.Display; import androidx.annotation.AnyThread; @@ -52,11 +51,12 @@ import androidx.annotation.UiThread; import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; -import com.android.launcher3.uioverrides.ApiWrapper; +import com.android.launcher3.util.window.CachedDisplayInfo; +import com.android.launcher3.util.window.WindowManagerProxy; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Map; +import java.util.Collections; import java.util.Objects; import java.util.Set; @@ -89,6 +89,7 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { // Null for SDK < S private final Context mWindowContext; + // The callback in this listener updates DeviceProfile, which other listeners might depend on private DisplayInfoChangeListener mPriorityListener; private final ArrayList mListeners = new ArrayList<>(); @@ -115,23 +116,9 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { mContext.registerReceiver(mReceiver, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED)); + WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context); mInfo = new Info(getDisplayInfoContext(display), display, - getInternalDisplays(mDM), emptyMap()); - } - - private static ArrayMap getInternalDisplays( - DisplayManager displayManager) { - Display[] displays = displayManager.getDisplays(); - ArrayMap internalDisplays = new ArrayMap<>(); - for (Display display : displays) { - if (ApiWrapper.isInternalDisplay(display)) { - Point size = new Point(); - display.getRealSize(size); - internalDisplays.put(ApiWrapper.getUniqueId(display), - new PortraitSize(size.x, size.y)); - } - } - return internalDisplays; + wmProxy, wmProxy.estimateInternalDisplayBounds(context)); } /** @@ -226,16 +213,17 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { @AnyThread private void handleInfoChange(Display display) { + WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext); Info oldInfo = mInfo; Context displayContext = getDisplayInfoContext(display); - Info newInfo = new Info(displayContext, display, - oldInfo.mInternalDisplays, oldInfo.mPerDisplayBounds); + Info newInfo = new Info(displayContext, display, wmProxy, oldInfo.mPerDisplayBounds); if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale || newInfo.navigationMode != oldInfo.navigationMode) { // Cache may not be valid anymore, recreate without cache - newInfo = new Info(displayContext, display, getInternalDisplays(mDM), emptyMap()); + newInfo = new Info(displayContext, display, wmProxy, + wmProxy.estimateInternalDisplayBounds(displayContext)); } int change = 0; @@ -254,9 +242,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)) { change |= CHANGE_SUPPORTED_BOUNDS; - PortraitSize realSize = new PortraitSize(newInfo.currentSize.x, newInfo.currentSize.y); - PortraitSize expectedSize = oldInfo.mInternalDisplays.get( - ApiWrapper.getUniqueId(display)); + Point currentS = newInfo.currentSize; + Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size; if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) { Log.e("b/198965093", "Inconsistent number of displays" @@ -264,7 +251,9 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { + "\noldInfo.supportedBounds: " + oldInfo.supportedBounds + "\nnewInfo.supportedBounds: " + newInfo.supportedBounds); } - if (!realSize.equals(expectedSize) && display.getState() == Display.STATE_OFF) { + if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y) + || Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y)) + && display.getState() == Display.STATE_OFF) { Log.e("b/198965093", "Display size changed while display is off, ignoring change"); return; } @@ -290,30 +279,38 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { public static class Info { - // Configuration properties + // Cached property public final int rotation; + public final String displayId; + public final Point currentSize; + public final Rect cutout; + + // Configuration property public final float fontScale; public final int densityDpi; public final NavigationMode navigationMode; private final PortraitSize mScreenSizeDp; - public final Point currentSize; - - public String displayId; public final Set supportedBounds = new ArraySet<>(); - private final Map> mPerDisplayBounds = new ArrayMap<>(); - private final ArrayMap mInternalDisplays; + + private final ArrayMap> mPerDisplayBounds = + new ArrayMap<>(); public Info(Context context, Display display) { - this(context, display, new ArrayMap<>(), emptyMap()); + /* don't need system overrides for external displays */ + this(context, display, new WindowManagerProxy(), new ArrayMap<>()); } - private Info(Context context, Display display, - ArrayMap internalDisplays, - Map> perDisplayBoundsCache) { - mInternalDisplays = internalDisplays; - rotation = display.getRotation(); + // Used for testing + public Info(Context context, Display display, + WindowManagerProxy wmProxy, + ArrayMap> perDisplayBoundsCache) { + CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(display); + rotation = displayInfo.rotation; + currentSize = displayInfo.size; + displayId = displayInfo.id; + cutout = displayInfo.cutout; Configuration config = context.getResources().getConfiguration(); fontScale = config.fontScale; @@ -321,54 +318,29 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp); navigationMode = parseNavigationMode(context); - currentSize = new Point(); - display.getRealSize(currentSize); + mPerDisplayBounds.putAll(perDisplayBoundsCache); + Pair cachedValue = mPerDisplayBounds.get(displayId); - displayId = ApiWrapper.getUniqueId(display); - Set currentSupportedBounds = - getSupportedBoundsForDisplay(display, currentSize); - mPerDisplayBounds.put(displayId, currentSupportedBounds); - supportedBounds.addAll(currentSupportedBounds); - - if (ApiWrapper.isInternalDisplay(display) && internalDisplays.size() > 1) { - int displayCount = internalDisplays.size(); - for (int i = 0; i < displayCount; i++) { - String displayKey = internalDisplays.keyAt(i); - if (TextUtils.equals(displayId, displayKey)) { - continue; - } - - Set displayBounds = perDisplayBoundsCache.get(displayKey); - if (displayBounds == null) { - // We assume densityDpi is the same across all internal displays - displayBounds = WindowManagerCompat.estimateDisplayProfiles( - context, internalDisplays.valueAt(i), densityDpi, - ApiWrapper.TASKBAR_DRAWN_IN_PROCESS); - } - - supportedBounds.addAll(displayBounds); - mPerDisplayBounds.put(displayKey, displayBounds); + WindowBounds realBounds = wmProxy.getRealBounds(context, display, displayInfo); + if (cachedValue == null) { + supportedBounds.add(realBounds); + } else { + // Verify that the real bounds are a match + WindowBounds expectedBounds = cachedValue.second[displayInfo.rotation]; + if (!realBounds.equals(expectedBounds)) { + WindowBounds[] clone = new WindowBounds[4]; + System.arraycopy(cachedValue.second, 0, clone, 0, 4); + clone[displayInfo.rotation] = realBounds; + cachedValue = Pair.create(displayInfo.normalize(), clone); + mPerDisplayBounds.put(displayId, cachedValue); } } + mPerDisplayBounds.values().forEach( + pair -> Collections.addAll(supportedBounds, pair.second)); Log.d("b/211775278", "displayId: " + displayId + ", currentSize: " + currentSize); Log.d("b/211775278", "perDisplayBounds: " + mPerDisplayBounds); } - private static Set getSupportedBoundsForDisplay(Display display, Point size) { - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); - - int portraitWidth = Math.min(size.x, size.y); - int portraitHeight = Math.max(size.x, size.y); - Set result = new ArraySet<>(); - result.add(new WindowBounds(portraitWidth, portraitHeight, - smallestSize.x, largestSize.y)); - result.add(new WindowBounds(portraitHeight, portraitWidth, - largestSize.x, smallestSize.y)); - return result; - } - /** * Returns {@code true} if the bounds represent a tablet. */ diff --git a/src/com/android/launcher3/util/RotationUtils.java b/src/com/android/launcher3/util/RotationUtils.java new file mode 100644 index 0000000000..3414a3de37 --- /dev/null +++ b/src/com/android/launcher3/util/RotationUtils.java @@ -0,0 +1,75 @@ +/* + * 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.launcher3.util; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.graphics.Point; +import android.graphics.Rect; + +/** + * Utility methods based on {@code frameworks/base/core/java/android/util/RotationUtils.java} + */ +public class RotationUtils { + + /** + * Rotates an Rect according to the given rotation. + */ + public static void rotateRect(Rect rect, int rotation) { + switch (rotation) { + case ROTATION_0: + return; + case ROTATION_90: + rect.set(rect.top, rect.right, rect.bottom, rect.left); + return; + case ROTATION_180: + rect.set(rect.right, rect.bottom, rect.left, rect.top); + return; + case ROTATION_270: + rect.set(rect.bottom, rect.left, rect.top, rect.right); + return; + default: + throw new IllegalArgumentException("unknown rotation: " + rotation); + } + } + + /** + * Rotates an size according to the given rotation. + */ + public static void rotateSize(Point size, int rotation) { + switch (rotation) { + case ROTATION_0: + case ROTATION_180: + return; + case ROTATION_90: + case ROTATION_270: + size.set(size.y, size.x); + return; + default: + throw new IllegalArgumentException("unknown rotation: " + rotation); + } + } + + /** @return the rotation needed to rotate from oldRotation to newRotation. */ + public static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } +} diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java index c92770e8fd..a15679a822 100644 --- a/src/com/android/launcher3/util/WindowBounds.java +++ b/src/com/android/launcher3/util/WindowBounds.java @@ -33,19 +33,27 @@ public class WindowBounds { public final Rect bounds; public final Rect insets; public final Point availableSize; + public final int rotationHint; public WindowBounds(Rect bounds, Rect insets) { + this(bounds, insets, -1); + } + + public WindowBounds(Rect bounds, Rect insets, int rotationHint) { this.bounds = bounds; this.insets = insets; + this.rotationHint = rotationHint; availableSize = new Point(bounds.width() - insets.left - insets.right, bounds.height() - insets.top - insets.bottom); } - public WindowBounds(int width, int height, int availableWidth, int availableHeight) { + public WindowBounds(int width, int height, int availableWidth, int availableHeight, + int rotationHint) { this.bounds = new Rect(0, 0, width, height); this.availableSize = new Point(availableWidth, availableHeight); // We don't care about insets in this case this.insets = new Rect(0, 0, width - availableWidth, height - availableHeight); + this.rotationHint = rotationHint; } @Override diff --git a/src/com/android/launcher3/util/WindowManagerCompat.java b/src/com/android/launcher3/util/WindowManagerCompat.java deleted file mode 100644 index 873a5189b2..0000000000 --- a/src/com/android/launcher3/util/WindowManagerCompat.java +++ /dev/null @@ -1,109 +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 com.android.launcher3.util; - -import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE; -import static com.android.launcher3.Utilities.dpiFromPx; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Insets; -import android.graphics.Rect; -import android.os.Build; -import android.util.ArraySet; -import android.view.WindowInsets; -import android.view.WindowInsets.Type; -import android.view.WindowManager; -import android.view.WindowMetrics; - -import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; -import com.android.launcher3.Utilities; -import com.android.launcher3.util.DisplayController.PortraitSize; - -import java.util.Collections; -import java.util.Set; - -/** - * Utility class to estimate window manager values - */ -@TargetApi(Build.VERSION_CODES.S) -public class WindowManagerCompat { - - public static final int MIN_TABLET_WIDTH = 600; - public static final int MIN_LARGE_TABLET_WIDTH = 720; - - /** - * Returns a set of supported render sizes for a internal display. - * This is a temporary workaround which assumes only nav-bar insets change across displays, and - * is only used until we eventually get the real values - * @param consumeTaskBar if true, it assumes that task bar is part of the app window - * and ignores any insets because of task bar. - */ - public static Set estimateDisplayProfiles( - Context windowContext, PortraitSize size, int densityDpi, boolean consumeTaskBar) { - if (!Utilities.ATLEAST_S) { - return Collections.emptySet(); - } - WindowInsets defaultInsets = windowContext.getSystemService(WindowManager.class) - .getMaximumWindowMetrics().getWindowInsets(); - boolean isGesturalMode = ResourceUtils.getIntegerByName( - "config_navBarInteractionMode", - windowContext.getResources(), - INVALID_RESOURCE_HANDLE) == 2; - - WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(defaultInsets); - Set result = new ArraySet<>(); - int swDP = (int) dpiFromPx(size.width, densityDpi); - boolean isTablet = swDP >= MIN_TABLET_WIDTH; - - final Insets portraitNav, landscapeNav; - if (isTablet && !consumeTaskBar) { - portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources() - .getDimensionPixelSize(R.dimen.taskbar_size)); - } else if (!isGesturalMode) { - portraitNav = Insets.of(0, 0, 0, - getSystemResource(windowContext, "navigation_bar_height", swDP)); - landscapeNav = isTablet - ? Insets.of(0, 0, 0, getSystemResource(windowContext, - "navigation_bar_height_landscape", swDP)) - : Insets.of(0, 0, getSystemResource(windowContext, - "navigation_bar_width", swDP), 0); - } else { - portraitNav = landscapeNav = Insets.of(0, 0, 0, 0); - } - - result.add(WindowBounds.fromWindowMetrics(new WindowMetrics( - new Rect(0, 0, size.width, size.height), - insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build()))); - result.add(WindowBounds.fromWindowMetrics(new WindowMetrics( - new Rect(0, 0, size.height, size.width), - insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build()))); - return result; - } - - private static int getSystemResource(Context context, String key, int swDp) { - int resourceId = context.getResources().getIdentifier(key, "dimen", "android"); - if (resourceId > 0) { - Configuration conf = new Configuration(); - conf.smallestScreenWidthDp = swDp; - return context.createConfigurationContext(conf) - .getResources().getDimensionPixelSize(resourceId); - } - return 0; - } -} diff --git a/src/com/android/launcher3/util/window/CachedDisplayInfo.java b/src/com/android/launcher3/util/window/CachedDisplayInfo.java new file mode 100644 index 0000000000..06b9829270 --- /dev/null +++ b/src/com/android/launcher3/util/window/CachedDisplayInfo.java @@ -0,0 +1,92 @@ +/* + * 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.launcher3.util.window; + +import static com.android.launcher3.util.RotationUtils.deltaRotation; +import static com.android.launcher3.util.RotationUtils.rotateRect; +import static com.android.launcher3.util.RotationUtils.rotateSize; + +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Surface; + +import java.util.Objects; + +/** + * Properties on a display + */ +public class CachedDisplayInfo { + + public final String id; + public final Point size; + public final int rotation; + public final Rect cutout; + + public CachedDisplayInfo() { + this(new Point(0, 0), 0); + } + + public CachedDisplayInfo(Point size, int rotation) { + this("", size, rotation, new Rect()); + } + + public CachedDisplayInfo(String id, Point size, int rotation, Rect cutout) { + this.id = id; + this.size = size; + this.rotation = rotation; + this.cutout = cutout; + } + + /** + * Returns a CachedDisplayInfo where the properties are normalized to {@link Surface#ROTATION_0} + */ + public CachedDisplayInfo normalize() { + if (rotation == Surface.ROTATION_0) { + return this; + } + Point newSize = new Point(size); + rotateSize(newSize, deltaRotation(rotation, Surface.ROTATION_0)); + + Rect newCutout = new Rect(cutout); + rotateRect(newCutout, deltaRotation(rotation, Surface.ROTATION_0)); + return new CachedDisplayInfo(id, newSize, Surface.ROTATION_0, newCutout); + } + + @Override + public String toString() { + return "CachedDisplayInfo{" + + "id='" + id + '\'' + + ", size=" + size + + ", rotation=" + rotation + + ", cutout=" + cutout + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CachedDisplayInfo)) return false; + CachedDisplayInfo that = (CachedDisplayInfo) o; + return rotation == that.rotation && Objects.equals(id, that.id) + && Objects.equals(size, that.size) && Objects.equals(cutout, + that.cutout); + } + + @Override + public int hashCode() { + return Objects.hash(id, size, rotation, cutout); + } +} diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java new file mode 100644 index 0000000000..ba3d9814ec --- /dev/null +++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java @@ -0,0 +1,311 @@ +/* + * 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.launcher3.util.window; + +import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE; +import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT; +import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE; +import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE; +import static com.android.launcher3.ResourceUtils.getDimenByName; +import static com.android.launcher3.Utilities.dpiFromPx; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; +import static com.android.launcher3.util.RotationUtils.deltaRotation; +import static com.android.launcher3.util.RotationUtils.rotateRect; +import static com.android.launcher3.util.RotationUtils.rotateSize; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.os.Build; +import android.util.ArrayMap; +import android.util.Pair; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.Surface; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import com.android.launcher3.R; +import com.android.launcher3.ResourceUtils; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.ResourceBasedOverride; +import com.android.launcher3.util.WindowBounds; + +/** + * Utility class for mocking some window manager behaviours + */ +public class WindowManagerProxy implements ResourceBasedOverride { + + public static final int MIN_TABLET_WIDTH = 600; + public static final int MIN_LARGE_TABLET_WIDTH = 720; + + public static final MainThreadInitializedObject INSTANCE = + forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class); + + protected final boolean mTaskbarDrawnInProcess; + + /** + * Creates a new instance of proxy, applying any overrides + */ + public static WindowManagerProxy newInstance(Context context) { + return Overrides.getObject(WindowManagerProxy.class, context, + R.string.window_manager_proxy_class); + } + + public WindowManagerProxy() { + this(false); + } + + protected WindowManagerProxy(boolean taskbarDrawnInProcess) { + mTaskbarDrawnInProcess = taskbarDrawnInProcess; + } + + /** + * Returns a map of normalized info of internal displays to estimated window bounds + * for that display + */ + public ArrayMap> estimateInternalDisplayBounds( + Context context) { + Display[] displays = context.getSystemService(DisplayManager.class).getDisplays(); + ArrayMap> result = new ArrayMap<>(); + for (Display display : displays) { + if (isInternalDisplay(display)) { + CachedDisplayInfo info = getDisplayInfo(display).normalize(); + WindowBounds[] bounds = estimateWindowBounds(context, info); + result.put(info.id, Pair.create(info, bounds)); + } + } + return result; + } + + /** + * Returns the real bounds for the provided display after applying any insets normalization + */ + @TargetApi(Build.VERSION_CODES.R) + public WindowBounds getRealBounds(Context windowContext, + Display display, CachedDisplayInfo info) { + if (!Utilities.ATLEAST_R) { + Point smallestSize = new Point(); + Point largestSize = new Point(); + display.getCurrentSizeRange(smallestSize, largestSize); + + if (info.size.y > info.size.x) { + // Portrait + return new WindowBounds(info.size.x, info.size.y, smallestSize.x, largestSize.y, + info.rotation); + } else { + // Landscape + new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y, + info.rotation); + } + } + + WindowMetrics wm = windowContext.getSystemService(WindowManager.class) + .getCurrentWindowMetrics(); + + Rect insets = new Rect(); + normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets); + return new WindowBounds(wm.getBounds(), insets, info.rotation); + } + + /** + * Returns an updated insets, accounting for various Launcher UI specific overrides like taskbar + */ + @TargetApi(Build.VERSION_CODES.R) + public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets, + Rect outInsets) { + if (!Utilities.ATLEAST_R || !mTaskbarDrawnInProcess) { + outInsets.set(oldInsets.getSystemWindowInsetLeft(), oldInsets.getSystemWindowInsetTop(), + oldInsets.getSystemWindowInsetRight(), oldInsets.getSystemWindowInsetBottom()); + return oldInsets; + } + + WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(oldInsets); + Insets navInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars()); + + Resources systemRes = context.getResources(); + Configuration config = systemRes.getConfiguration(); + + boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH; + boolean isGesture = isGestureNav(context); + + int bottomNav = isTablet + ? 0 + : (config.screenHeightDp > config.screenWidthDp + ? getDimenByName(NAVBAR_HEIGHT, systemRes, 0) + : (isGesture + ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) + : 0)); + Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav); + insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets); + insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets); + + // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar + // would count towards it). This is used for the bottom protection in All Apps for example. + if (isGesture) { + Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement()); + Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top, + oldTappableInsets.right, 0); + insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets); + } + + WindowInsets result = insetsBuilder.build(); + Insets systemWindowInsets = result.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); + outInsets.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right, + systemWindowInsets.bottom); + return result; + } + + /** + * Returns true if the display is an internal displays + */ + protected boolean isInternalDisplay(Display display) { + return display.getDisplayId() == Display.DEFAULT_DISPLAY; + } + + /** + * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations + */ + public WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo display) { + int densityDpi = context.getResources().getConfiguration().densityDpi; + int rotation = display.rotation; + Rect safeCutout = display.cutout; + + int minSize = Math.min(display.size.x, display.size.y); + int swDp = (int) dpiFromPx(minSize, densityDpi); + + Resources systemRes; + { + Configuration conf = new Configuration(); + conf.smallestScreenWidthDp = swDp; + systemRes = context.createConfigurationContext(conf).getResources(); + } + + boolean isTablet = swDp >= MIN_TABLET_WIDTH; + boolean isTabletOrGesture = isTablet + || (Utilities.ATLEAST_R && isGestureNav(context)); + + int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0); + + int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape; + + navBarHeightPortrait = isTablet + ? (mTaskbarDrawnInProcess + ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size)) + : getDimenByName(NAVBAR_HEIGHT, systemRes, 0); + + navBarHeightLandscape = isTablet + ? (mTaskbarDrawnInProcess + ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size)) + : (isTabletOrGesture + ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0); + navbarWidthLandscape = isTabletOrGesture + ? 0 + : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0); + + WindowBounds[] result = new WindowBounds[4]; + Point tempSize = new Point(); + for (int i = 0; i < 4; i++) { + int rotationChange = deltaRotation(rotation, i); + tempSize.set(display.size.x, display.size.y); + rotateSize(tempSize, rotationChange); + Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y); + + int navBarHeight, navbarWidth; + if (tempSize.y > tempSize.x) { + navBarHeight = navBarHeightPortrait; + navbarWidth = 0; + } else { + navBarHeight = navBarHeightLandscape; + navbarWidth = navbarWidthLandscape; + } + + Rect insets = new Rect(safeCutout); + rotateRect(insets, rotationChange); + insets.top = Math.max(insets.top, statusBarHeight); + insets.bottom = Math.max(insets.bottom, navBarHeight); + + if (i == Surface.ROTATION_270 || i == Surface.ROTATION_180) { + // On reverse landscape (and in rare-case when the natural orientation of the + // device is landscape), navigation bar is on the right. + insets.left = Math.max(insets.left, navbarWidth); + } else { + insets.right = Math.max(insets.right, navbarWidth); + } + result[i] = new WindowBounds(bounds, insets, i); + } + return result; + } + + protected boolean isGestureNav(Context context) { + return ResourceUtils.getIntegerByName("config_navBarInteractionMode", + context.getResources(), INVALID_RESOURCE_HANDLE) == 2; + } + + /** + * Returns a CachedDisplayInfo initialized for the current display + */ + @TargetApi(Build.VERSION_CODES.S) + public CachedDisplayInfo getDisplayInfo(Display display) { + int rotation = display.getRotation(); + + Point size = new Point(); + display.getRealSize(size); + + Rect cutoutRect = new Rect(); + if (Utilities.ATLEAST_S) { + DisplayCutout cutout = display.getCutout(); + if (cutout != null) { + cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), + cutout.getSafeInsetRight(), cutout.getSafeInsetBottom()); + } + } + + return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect); + } + + /** + * Returns a unique ID representing the display + */ + protected String getDisplayId(Display display) { + return Integer.toString(display.getDisplayId()); + } + + /** + * Returns rotation of the display associated with the context. + */ + public int getRotation(Context context) { + Display d = null; + if (Utilities.ATLEAST_R) { + try { + d = context.getDisplay(); + } catch (UnsupportedOperationException e) { + // Ignore + } + } + return d == null ? DisplayController.INSTANCE.get(context).getInfo().rotation + : d.getRotation(); + } +} diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java index 81e3f988ba..67157494d8 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java @@ -19,7 +19,6 @@ package com.android.launcher3.uioverrides; import android.app.Person; import android.content.Context; import android.content.pm.ShortcutInfo; -import android.view.Display; import com.android.launcher3.Utilities; @@ -31,20 +30,6 @@ public class ApiWrapper { return Utilities.EMPTY_PERSON_ARRAY; } - /** - * Returns true if the display is an internal displays - */ - public static boolean isInternalDisplay(Display display) { - return display.getDisplayId() == Display.DEFAULT_DISPLAY; - } - - /** - * Returns a unique ID representing the display - */ - public static String getUniqueId(Display display) { - return Integer.toString(display.getDisplayId()); - } - /** * Returns the minimum space that should be left empty at the end of hotseat */ diff --git a/tests/src/com/android/launcher3/DeviceProfileTest.kt b/tests/src/com/android/launcher3/DeviceProfileTest.kt index 60046a0768..75ad21d8aa 100644 --- a/tests/src/com/android/launcher3/DeviceProfileTest.kt +++ b/tests/src/com/android/launcher3/DeviceProfileTest.kt @@ -139,7 +139,7 @@ class DeviceProfileTest { else Pair(1440, 3120) - windowBounds = WindowBounds(x, y, x, y - 100) + windowBounds = WindowBounds(x, y, x, y - 100, 0) `when`(info.isTablet(any())).thenReturn(false) `when`(info.isLargeTablet(any())).thenReturn(false) @@ -153,7 +153,7 @@ class DeviceProfileTest { else Pair(1600, 2560) - windowBounds = WindowBounds(x, y, x, y - 100) + windowBounds = WindowBounds(x, y, x, y - 100, 0) `when`(info.isTablet(any())).thenReturn(true) `when`(info.isLargeTablet(any())).thenReturn(false) @@ -167,7 +167,7 @@ class DeviceProfileTest { else Pair(1600, 2560) - windowBounds = WindowBounds(x, y, x, y - 100) + windowBounds = WindowBounds(x, y, x, y - 100, 0) `when`(info.isTablet(any())).thenReturn(true) `when`(info.isLargeTablet(any())).thenReturn(true) diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java index 59966eee04..33249592c2 100644 --- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -67,6 +67,7 @@ import com.android.launcher3.pm.UserCache; import com.android.launcher3.testing.TestInformationProvider; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; +import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.widget.custom.CustomWidgetManager; import org.mockito.ArgumentCaptor; @@ -501,7 +502,7 @@ public class LauncherModelHelper { LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, DisplayController.INSTANCE, CustomWidgetManager.INSTANCE, SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, - ItemInstallQueue.INSTANCE); + ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE); mPm = spy(getBaseContext().getPackageManager()); mDbDir = new File(getCacheDir(), UUID.randomUUID().toString()); }