Merge "Updating window manager estimation logic:" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-03-09 22:24:12 +00:00
committed by Android (Google) Code Review
25 changed files with 683 additions and 431 deletions
+1 -1
View File
@@ -23,8 +23,8 @@
</string-array>
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
<string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
@@ -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);
}
}
}
@@ -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
*/
@@ -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<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
new HashMap<CurrentDisplay, OrientationRectF>();
private final Map<CachedDisplayInfo, OrientationRectF> mSwipeTouchRegions =
new HashMap<CachedDisplayInfo, OrientationRectF>();
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(" ");
}
@@ -606,7 +606,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) {
@@ -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();
}
}
@@ -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<String, Pair<CachedDisplayInfo, WindowBounds[]>> 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,
+1
View File
@@ -69,6 +69,7 @@
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
<string name="model_delegate_class" translatable="false"></string>
<string name="window_manager_proxy_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
@@ -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
+4 -2
View File
@@ -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;
@@ -548,8 +550,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)
@@ -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<OnIDPChangeListener> 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;
@@ -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
@@ -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);
}
@@ -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,
@@ -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<STATE_TYPE extends BaseState<STATE_TYPE>>
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.
*/
@@ -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;
@@ -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<DisplayInfoChangeListener> 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<String, PortraitSize> getInternalDisplays(
DisplayManager displayManager) {
Display[] displays = displayManager.getDisplays();
ArrayMap<String, PortraitSize> 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<WindowBounds> supportedBounds = new ArraySet<>();
private final Map<String, Set<WindowBounds>> mPerDisplayBounds = new ArrayMap<>();
private final ArrayMap<String, PortraitSize> mInternalDisplays;
private final ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> 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<String, PortraitSize> internalDisplays,
Map<String, Set<WindowBounds>> perDisplayBoundsCache) {
mInternalDisplays = internalDisplays;
rotation = display.getRotation();
// Used for testing
public Info(Context context, Display display,
WindowManagerProxy wmProxy,
ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> 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<CachedDisplayInfo, WindowBounds[]> cachedValue = mPerDisplayBounds.get(displayId);
displayId = ApiWrapper.getUniqueId(display);
Set<WindowBounds> 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<WindowBounds> 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<WindowBounds> 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<WindowBounds> 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.
*/
@@ -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;
}
}
@@ -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
@@ -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<WindowBounds> 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<WindowBounds> 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;
}
}
@@ -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);
}
}
@@ -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<WindowManagerProxy> 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<String, Pair<CachedDisplayInfo, WindowBounds[]>> estimateInternalDisplayBounds(
Context context) {
Display[] displays = context.getSystemService(DisplayManager.class).getDisplays();
ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> 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();
}
}
@@ -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
*/
@@ -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)
@@ -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());
}