Merge "Adding support to parse deep-shortcuts." into tm-dev
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 4e3c02c..2cd48d5 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,8 +15,7 @@
*/
-->
<resources>
- <dimen name="overview_actions_top_margin_gesture">19.1dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">10dp</dimen>
+ <dimen name="overview_actions_top_margin">12dp</dimen>
<dimen name="overview_grid_side_margin">52dp</dimen>
<dimen name="overview_page_spacing">38dp</dimen>
</resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 223a5e9..5153afa 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -20,8 +20,6 @@
<dimen name="overview_task_margin">12dp</dimen>
<dimen name="overview_task_margin_grid">4dp</dimen>
<dimen name="overview_actions_button_spacing">36dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">19.37dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">22dp</dimen>
<dimen name="overview_grid_side_margin">60dp</dimen>
<dimen name="overview_grid_row_spacing">36dp</dimen>
<dimen name="overview_page_spacing">36dp</dimen>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3f08cf3..4210052 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -35,13 +35,12 @@
<dimen name="overview_task_margin">16dp</dimen>
<dimen name="overview_task_margin_grid">0dp</dimen>
<item name="overview_max_scale" format="float" type="dimen">0.7</item>
+ <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
<!-- Overrideable in overlay that provides the Overview Actions. -->
<dimen name="overview_actions_height">48dp</dimen>
<dimen name="overview_actions_button_spacing">32dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">28dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">28dp</dimen>
- <dimen name="overview_actions_margin_three_button">8dp</dimen>
+ <dimen name="overview_actions_top_margin">24dp</dimen>
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
<dimen name="overview_page_spacing">16dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index fe24c4b..6abcbd5 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -296,7 +296,7 @@
new SplitSelectStateController(this, mHandler, getStateManager(),
getDepthController());
overviewPanel.init(mActionsView, controller);
- mActionsView.setDp(getDeviceProfile());
+ mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
mAppTransitionManager = new QuickstepTransitionManager(this);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 9686510..48127c0 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.util.DisplayController.getNavigationMode;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
@@ -62,7 +61,6 @@
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.SplitScreenBounds;
-import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -224,7 +222,7 @@
Resources res = context.getResources();
if (dp.isTablet) {
Rect gridRect = new Rect();
- calculateGridSize(context, dp, gridRect);
+ calculateGridSize(dp, gridRect);
PointF taskDimension = getTaskDimension(context, dp);
float scale = gridRect.height() / taskDimension.y;
@@ -238,15 +236,15 @@
int taskMargin = dp.overviewTaskMarginPx;
calculateTaskSizeInternal(context, dp,
dp.overviewTaskThumbnailTopMarginPx,
- getOverviewActionsHeight(context, dp),
+ dp.getOverviewActionsClaimedSpace(),
res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+ Gravity.CENTER,
outRect);
}
}
- private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
- Rect outRect) {
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
+ int claimedSpaceBelow, int minimumHorizontalPadding, int gravity, Rect outRect) {
PointF taskDimension = getTaskDimension(context, dp);
Rect insets = dp.getInsets();
@@ -264,7 +262,7 @@
int outWidth = Math.round(scale * taskDimension.x);
int outHeight = Math.round(scale * taskDimension.y);
- Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+ Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
}
private static PointF getTaskDimension(Context context, DeviceProfile dp) {
@@ -314,10 +312,10 @@
/**
* Calculates the overview grid size for the provided device configuration.
*/
- public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+ public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
Rect insets = dp.getInsets();
int topMargin = dp.overviewTaskThumbnailTopMarginPx;
- int bottomMargin = getOverviewActionsHeight(context, dp);
+ int bottomMargin = dp.getOverviewActionsClaimedSpace();
int sideMargin = dp.overviewGridSideMargin;
outRect.set(0, 0, dp.widthPx, dp.heightPx);
@@ -352,21 +350,17 @@
* Calculates the modal taskView size for the provided device configuration
*/
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ calculateTaskSize(context, dp, outRect);
+ float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
calculateTaskSizeInternal(
context, dp,
dp.overviewTaskMarginPx,
- getOverviewActionsHeight(context, dp),
- dp.overviewTaskMarginPx,
+ dp.heightPx - outRect.bottom - dp.getInsets().bottom,
+ Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
outRect);
}
- /** Gets the space that the overview actions will take, including bottom margin. */
- private int getOverviewActionsHeight(Context context, DeviceProfile dp) {
- return OverviewActionsView.getOverviewActionsBottomMarginPx(getNavigationMode(context), dp)
- + OverviewActionsView.getOverviewActionsTopMarginPx(getNavigationMode(context), dp)
- + dp.overviewActionsHeight;
- }
-
/**
* Called when the gesture ends and the animation starts towards the given target. Used to add
* an optional additional animation with the same duration.
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index c9ee2db..3e68c7f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -221,6 +221,9 @@
setOverviewStateEnabled(true);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.isFullScreen());
+ if (toState == MODAL_TASK) {
+ setOverviewSelectEnabled(true);
+ }
Log.d(BAD_STATE, "FRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
setFreezeViewVisibility(true);
}
@@ -236,6 +239,9 @@
Log.d(BAD_STATE, "FRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+ finalState);
setFreezeViewVisibility(false);
+ if (finalState != MODAL_TASK) {
+ setOverviewSelectEnabled(false);
+ }
if (isOverlayEnabled) {
runActionOnRemoteHandles(remoteTargetHandle ->
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 45aaf35..306ebd7 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -100,6 +100,9 @@
setOverviewStateEnabled(toState.overviewUi);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
+ if (toState == OVERVIEW_MODAL_TASK) {
+ setOverviewSelectEnabled(true);
+ }
Log.d(BAD_STATE, "LRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
setFreezeViewVisibility(true);
}
@@ -115,6 +118,9 @@
Log.d(BAD_STATE, "LRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+ finalState);
setFreezeViewVisibility(false);
+ if (finalState != OVERVIEW_MODAL_TASK) {
+ setOverviewSelectEnabled(false);
+ }
if (isOverlayEnabled) {
runActionOnRemoteHandles(remoteTargetHandle ->
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 99a2d6f..49a540f 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,8 +16,6 @@
package com.android.quickstep.views;
-import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -99,6 +97,7 @@
@Nullable
protected DeviceProfile mDp;
+ private final Rect mTaskSize = new Rect();
public OverviewActionsView(Context context) {
this(context, null);
@@ -202,8 +201,10 @@
* Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
*/
private void updatePadding() {
- boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
- DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS;
+ if (mDp == null) {
+ return;
+ }
+ boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && !mDp.isGestureMode;
if (alignFor3ButtonTaskbar) {
// Add extra horizontal spacing
int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
@@ -225,15 +226,34 @@
LayoutParams actionParams = (LayoutParams) findViewById(
R.id.action_buttons).getLayoutParams();
actionParams.setMargins(
- actionParams.leftMargin, getOverviewActionsTopMarginPx(mode, mDp),
- actionParams.rightMargin, getOverviewActionsBottomMarginPx(mode, mDp));
+ actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
+ actionParams.rightMargin, getBottomMargin());
+ }
+
+ private int getBottomMargin() {
+ if (mDp == null) {
+ return 0;
+ }
+
+ if (mDp.isVerticalBarLayout()) {
+ return mDp.getInsets().bottom;
+ }
+
+ if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
+ return mDp.getOverviewActionsClaimedSpaceBelow();
+ }
+
+ // Align to bottom of task Rect.
+ return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
+ - mDp.overviewActionsHeight;
}
/**
- * Set the device profile for this view to draw with.
+ * Updates device profile and task size for this view to draw with.
*/
- public void setDp(DeviceProfile dp) {
+ public void updateDimension(DeviceProfile dp, Rect taskSize) {
mDp = dp;
+ mTaskSize.set(taskSize);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -257,45 +277,4 @@
mSplitButton.setVisibility(visible ? VISIBLE : GONE);
findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
}
-
- /** Get the top margin associated with the action buttons in Overview. */
- public static int getOverviewActionsTopMarginPx(NavigationMode mode, DeviceProfile dp) {
- // In vertical bar, use the smaller task margin for the top regardless of mode
- if (dp.isVerticalBarLayout()) {
- return dp.overviewTaskMarginPx;
- }
-
- if (mode == NavigationMode.THREE_BUTTONS) {
- return dp.overviewActionsMarginThreeButtonPx;
- }
-
- return dp.overviewActionsTopMarginGesturePx;
- }
-
- /** Get the bottom margin associated with the action buttons in Overview. */
- public static int getOverviewActionsBottomMarginPx(NavigationMode mode, DeviceProfile dp) {
- int bottomInset = dp.getInsets().bottom;
-
- if (dp.isVerticalBarLayout()) {
- return bottomInset;
- }
-
- if (mode == NavigationMode.THREE_BUTTONS) {
- int bottomMargin = dp.overviewActionsMarginThreeButtonPx + bottomInset;
- if (dp.isTaskbarPresent) {
- // Align vertically, using taskbar height + mDp.taskbarOffsetY() to estimate where
- // the button nav top is.
- int actionsTop = (dp.heightPx - bottomMargin - bottomInset)
- - dp.overviewActionsHeight;
- int navTop = dp.heightPx - (dp.taskbarSize + dp.getTaskbarOffsetY());
- bottomMargin -=
- navTop - actionsTop + ((dp.taskbarSize - dp.overviewActionsHeight) / 2);
- }
- return bottomMargin;
- }
-
- // There is no bottom inset when taskbar is present, use stashed taskbar as padding instead.
- return dp.overviewActionsBottomMarginGesturePx
- + (dp.isTaskbarPresent ? dp.stashedTaskbarSize : bottomInset);
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 49bf827..5d71ebd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -455,6 +455,7 @@
protected boolean mFreezeViewVisibility;
private boolean mOverviewGridEnabled;
private boolean mOverviewFullscreenEnabled;
+ private boolean mOverviewSelectEnabled;
private float mAdjacentPageHorizontalOffset = 0;
protected float mTaskViewsSecondaryTranslation = 0;
@@ -1637,11 +1638,11 @@
// Propagate DeviceProfile change event.
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
- mActionsView.setDp(dp);
mOrientationState.setDeviceProfile(dp);
// Update RecentsView and TaskView's DeviceProfile dependent layout.
updateOrientationHandler();
+ mActionsView.updateDimension(dp, mLastComputedTaskSize);
}
private void updateOrientationHandler() {
@@ -1706,7 +1707,7 @@
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
- mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+ mSizeStrategy.calculateGridSize(mActivity.getDeviceProfile(),
mLastComputedGridSize);
mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedGridTaskSize, mOrientationHandler);
@@ -1764,7 +1765,8 @@
* Returns the size of task selected to enter modal state.
*/
public Point getSelectedTaskSize() {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(),
+ mTempRect);
return new Point(mTempRect.width(), mTempRect.height());
}
@@ -3689,10 +3691,7 @@
// Update the pivots such that when the task is scaled, it fills the full page
getTaskSize(mTempRect);
- getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
- mActivity.getDeviceProfile(), mTempPointF);
- setPivotX(mTempPointF.x);
- setPivotY(mTempPointF.y);
+ updatePivots();
setTaskModalness(mTaskModalness);
mLastComputedTaskStartPushOutDistance = null;
mLastComputedTaskEndPushOutDistance = null;
@@ -3704,6 +3703,18 @@
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
+ private void updatePivots() {
+ if (mOverviewSelectEnabled) {
+ setPivotX(mLastComputedTaskSize.centerX());
+ setPivotY(mLastComputedTaskSize.bottom);
+ } else {
+ getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
+ mActivity.getDeviceProfile(), mTempPointF);
+ setPivotX(mTempPointF.x);
+ setPivotY(mTempPointF.y);
+ }
+ }
+
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
@@ -4866,6 +4877,17 @@
}
/**
+ * Update whether RecentsView is in select mode. Should be enabled before transitioning to
+ * select mode, and only disabled after transitioning from select mode.
+ */
+ public void setOverviewSelectEnabled(boolean overviewSelectEnabled) {
+ if (mOverviewSelectEnabled != overviewSelectEnabled) {
+ mOverviewSelectEnabled = overviewSelectEnabled;
+ updatePivots();
+ }
+ }
+
+ /**
* Switch the current running task view to static snapshot mode,
* capturing the snapshot at the same time.
*/
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 52ff3f0..2c3f5ed 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -368,9 +368,7 @@
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="overview_actions_button_spacing">0dp</dimen>
<dimen name="overview_actions_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_margin_three_button">0dp</dimen>
+ <dimen name="overview_actions_top_margin">0dp</dimen>
<dimen name="overview_grid_side_margin">0dp</dimen>
<dimen name="overview_grid_row_spacing">0dp</dimen>
<dimen name="overview_page_spacing">0dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 33bb0a5..31f1da8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -195,9 +195,7 @@
public int overviewTaskIconDrawableSizeGridPx;
public int overviewTaskThumbnailTopMarginPx;
public final int overviewActionsHeight;
- public final int overviewActionsMarginThreeButtonPx;
- public final int overviewActionsTopMarginGesturePx;
- public final int overviewActionsBottomMarginGesturePx;
+ public final int overviewActionsTopMarginPx;
public final int overviewActionsButtonSpacing;
public int overviewPageSpacing;
public int overviewRowSpacing;
@@ -408,16 +406,14 @@
overviewTaskIconDrawableSizeGridPx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
- overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
- R.dimen.overview_actions_top_margin_gesture);
- overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_gesture);
+ // In vertical bar, use the smaller task margin for the top regardless of mode.
+ overviewActionsTopMarginPx = isVerticalBarLayout()
+ ? overviewTaskMarginPx
+ : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
overviewActionsButtonSpacing = res.getDimensionPixelSize(
R.dimen.overview_actions_button_spacing);
overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
- overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
- R.dimen.overview_actions_margin_three_button);
// Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but
// overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the
// extra margin when calculating row spacing.
@@ -658,7 +654,7 @@
Point workspacePadding = getTotalWorkspacePadding();
// Check to see if the icons fit within the available height.
- float usedHeight = getCellLayoutHeight();
+ float usedHeight = getCellLayoutHeightSpecification();
final int maxHeight = getWorkspaceHeight(workspacePadding);
float extraHeight = Math.max(0, maxHeight - usedHeight);
float scaleY = maxHeight / usedHeight;
@@ -669,7 +665,8 @@
// We scale to fit the cellWidth and cellHeight in the available space.
// The benefit of scalable grids is that we can get consistent aspect ratios between
// devices.
- float usedWidth = getCellLayoutWidth() + (desiredWorkspaceHorizontalMarginPx * 2);
+ float usedWidth =
+ getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2);
// We do not subtract padding here, as we also scale the workspace padding if needed.
scaleX = availableWidthPx / usedWidth;
shouldScale = true;
@@ -678,19 +675,19 @@
if (shouldScale) {
float scale = Math.min(scaleX, scaleY);
updateIconSize(scale, res);
- extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
+ extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
}
updateAvailableFolderCellDimensions(res);
return Math.round(extraHeight);
}
- private int getCellLayoutHeight() {
+ private int getCellLayoutHeightSpecification() {
return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
+ cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
}
- private int getCellLayoutWidth() {
+ private int getCellLayoutWidthSpecification() {
int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
+ cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
@@ -920,7 +917,7 @@
/**
* Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
*/
- public float getWorkspaceSpringLoadShrunkBottom() {
+ private float getWorkspaceSpringLoadShrunkBottom() {
int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
workspaceSpringLoadShrunkBottom =
heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -931,10 +928,30 @@
/**
* Gets the minimum visible amount of the next workspace page when in the spring-loaded state.
*/
- public float getWorkspaceSpringLoadedMinimumNextPageVisible() {
+ private float getWorkspaceSpringLoadedMinimumNextPageVisible() {
return getCellSize().x / 2f;
}
+ /**
+ * Gets the scale of the workspace for the spring-loaded edit state.
+ */
+ public float getWorkspaceSpringLoadScale() {
+ float cellLayoutHeight = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
+ float scale = (getWorkspaceSpringLoadShrunkBottom() - getWorkspaceSpringLoadShrunkTop())
+ / cellLayoutHeight;
+ scale = Math.min(scale, 1f);
+
+ // Reduce scale if next pages would not be visible after scaling the workspace
+ int workspaceWidth = getWorkspaceWidth();
+ float scaledWorkspaceWidth = workspaceWidth * scale;
+ float maxAvailableWidth =
+ workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
+ if (scaledWorkspaceWidth > maxAvailableWidth) {
+ scale *= maxAvailableWidth / scaledWorkspaceWidth;
+ }
+ return scale;
+ }
+
public int getWorkspaceWidth() {
return getWorkspaceWidth(getTotalWorkspacePadding());
}
@@ -1104,6 +1121,24 @@
}
/**
+ * Returns the number of pixels required below OverviewActions excluding insets.
+ */
+ public int getOverviewActionsClaimedSpaceBelow() {
+ if (isTaskbarPresent && !isGestureMode) {
+ // Align vertically to where nav buttons are.
+ return ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
+ }
+
+ return 0;
+ }
+
+ /** Gets the space that the overview actions will take, including bottom margin. */
+ public int getOverviewActionsClaimedSpace() {
+ return overviewActionsTopMarginPx + overviewActionsHeight
+ + getOverviewActionsClaimedSpaceBelow();
+ }
+
+ /**
* @return the bounds for which the open folders should be contained within
*/
public Rect getAbsoluteOpenFolderBounds() {
@@ -1323,12 +1358,10 @@
overviewTaskIconDrawableSizeGridPx));
writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
overviewTaskThumbnailTopMarginPx));
- writer.println(prefix + pxToDpStr("overviewActionsMarginThreeButtonPx",
- overviewActionsMarginThreeButtonPx));
- writer.println(prefix + pxToDpStr("overviewActionsTopMarginGesturePx",
- overviewActionsTopMarginGesturePx));
- writer.println(prefix + pxToDpStr("overviewActionsBottomMarginGesturePx",
- overviewActionsBottomMarginGesturePx));
+ writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
+ overviewActionsTopMarginPx));
+ writer.println(prefix + pxToDpStr("overviewActionsHeight",
+ overviewActionsHeight));
writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing",
overviewActionsButtonSpacing));
writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
@@ -1344,6 +1377,8 @@
prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
workspaceSpringLoadShrunkBottom));
+ writer.println(
+ prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
}
private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index ec3629d..2e3f26c 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -237,10 +237,8 @@
rightButton.getMeasuredHeight());
} else if (dp.isPhone) {
// Buttons aligned to outer edges of scaled workspace.
- float shrunkTop = dp.getWorkspaceSpringLoadShrunkTop();
- float shrunkBottom = dp.getWorkspaceSpringLoadShrunkBottom();
- float scale =
- (shrunkBottom - shrunkTop) / launcher.getWorkspace().getNormalChildHeight();
+ float scale = dp.getWorkspaceSpringLoadScale();
+
int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
int start = barCenter - (workspaceWidth / 2);
int end = barCenter + (workspaceWidth / 2);
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8358f2a..7b96838 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -273,6 +273,16 @@
}
/**
+ * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)}
+ * but accepts a Rect instead of float[].
+ */
+ public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) {
+ float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom};
+ mapCoordInSelfToDescendant(descendant, root, coords);
+ rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]);
+ }
+
+ /**
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
*/
public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ed01660..78771ce 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -200,6 +200,7 @@
private final int[] mTempXY = new int[2];
private final float[] mTempFXY = new float[2];
+ private final Rect mTempRect = new Rect();
@Thunk float[] mDragViewVisualCenter = new float[2];
private SpringLoadedDragController mSpringLoadedDragController;
@@ -906,7 +907,11 @@
* two panel UI is enabled.
*/
public int getScreenPair(int screenId) {
- if (screenId % 2 == 0) {
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ return EXTRA_EMPTY_SCREEN_SECOND_ID;
+ } else if (screenId == EXTRA_EMPTY_SCREEN_SECOND_ID) {
+ return EXTRA_EMPTY_SCREEN_ID;
+ } else if (screenId % 2 == 0) {
return screenId + 1;
} else {
return screenId - 1;
@@ -1730,7 +1735,7 @@
// If it's an external drop (e.g. from All Apps), check if it should be accepted
CellLayout dropTargetLayout = mDropToLayout;
if (d.dragSource != this) {
- // Don't accept the drop if we're not over a screen at time of drop
+ // Don't accept the drop if we're not over a valid drop target at time of drop
if (dropTargetLayout == null) {
return false;
}
@@ -2331,17 +2336,6 @@
xy[1] = xy[1] - v.getTop();
}
- boolean isPointInSelfOverHotseat(int x, int y) {
- mTempFXY[0] = x;
- mTempFXY[1] = y;
- mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
- View hotseat = mLauncher.getHotseat();
- return mTempFXY[0] >= hotseat.getLeft()
- && mTempFXY[0] <= hotseat.getRight()
- && mTempFXY[1] >= hotseat.getTop()
- && mTempFXY[1] <= hotseat.getBottom();
- }
-
/**
* Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
* @param layout either hotseat of a page in workspace
@@ -2379,7 +2373,7 @@
final View child = (mDragInfo == null) ? null : mDragInfo.cell;
if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
- if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ if (mDragTargetLayout == null || mLauncher.isHotseatLayout(mDragTargetLayout)) {
mSpringLoadedDragController.cancel();
} else {
mSpringLoadedDragController.setAlarm(mDragTargetLayout);
@@ -2458,52 +2452,25 @@
*/
private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
CellLayout layout = null;
- // Test to see if we are over the hotseat first
- if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
- if (isPointInSelfOverHotseat(d.x, d.y)) {
- layout = mLauncher.getHotseat();
+ if (shouldUseHotseatAsDropLayout(d)) {
+ layout = mLauncher.getHotseat();
+ } else if (!isDragObjectOverSmartSpace(d)) {
+ // If the object is over qsb/smartspace, we don't want to highlight anything.
+
+ // Check neighbour pages
+ layout = checkDragObjectIsOverNeighbourPages(d, centerX);
+
+ if (layout == null) {
+ // Check visible pages
+ IntSet visiblePageIndices = getVisiblePageIndices();
+ for (int visiblePageIndex : visiblePageIndices) {
+ layout = verifyInsidePage(visiblePageIndex, d.x, d.y);
+ if (layout != null) break;
+ }
}
}
- // Note, centerX represents the center of the object that is being dragged, visually. d.x
- // represents the location of the finger within the dragged item.
- float touchX;
- float touchY = d.y;
-
- // Go through the pages and check if the dragged item is inside one of them. This block
- // is responsible for determining whether we need to snap to a different screen.
- int nextPage = getNextPage();
- IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage
- + (isTwoPanelEnabled() ? 2 : 1));
- for (int pageIndex : pageIndexesToVerify) {
- if (layout != null || isPageInTransition()) {
- break;
- }
-
- // When deciding whether to perform a page switch, we need to consider the most extreme
- // X coordinate between the finger location and the center of the object being dragged.
- // This is either the max or the min of the two depending on whether dragging to the
- // left / right, respectively.
- touchX = ((((pageIndex < nextPage) && !mIsRtl) || pageIndex > nextPage && mIsRtl)
- ? Math.min(d.x, centerX) : Math.max(d.x, centerX));
- layout = verifyInsidePage(pageIndex, touchX, touchY);
- }
-
- // If the dragged item isn't located in one of the pages above, the icon will stay on the
- // current screen. For two panel pick the closest panel on the current screen,
- // on one panel just choose the current page.
- if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
- if (isTwoPanelEnabled()) {
- // When determining which panel to use within a single screen, we always use
- // the centroid of the object rather than the finger.
- touchX = centerX;
- nextPage = getScreenCenter(getScrollX()) > touchX
- ? (mIsRtl ? nextPage + 1 : nextPage) // left side
- : (mIsRtl ? nextPage : nextPage + 1); // right side
- }
- layout = (CellLayout) getChildAt(nextPage);
- }
-
+ // Update the current drop layout if the target changed
if (layout != mDragTargetLayout) {
setCurrentDropLayout(layout);
setCurrentDragOverlappingLayout(layout);
@@ -2512,6 +2479,69 @@
return false;
}
+ private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
+ if (mLauncher.getHotseat() == null
+ || mLauncher.getHotseat().getShortcutsAndWidgets() == null
+ || isDragWidget(dragObject)) {
+ return false;
+ }
+ View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
+ getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
+ return mTempRect.contains(dragObject.x, dragObject.y);
+ }
+
+ private boolean isDragObjectOverSmartSpace(DragObject dragObject) {
+ if (mQsb == null) {
+ return false;
+ }
+ getViewBoundsRelativeToWorkspace(mQsb, mTempRect);
+ return mTempRect.contains(dragObject.x, dragObject.y);
+ }
+
+ private CellLayout checkDragObjectIsOverNeighbourPages(DragObject d, float centerX) {
+ if (isPageInTransition()) {
+ return null;
+ }
+
+ // Check the workspace pages whether the object is over any of them
+
+ // Note, centerX represents the center of the object that is being dragged, visually.
+ // d.x represents the location of the finger within the dragged item.
+ float touchX;
+ float touchY = d.y;
+
+ // Go through the pages and check if the dragged item is inside one of them. This block
+ // is responsible for determining whether we need to snap to a different screen.
+ int nextPage = getNextPage();
+ IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1,
+ nextPage + (isTwoPanelEnabled() ? 2 : 1));
+
+ for (int pageIndex : pageIndexesToVerify) {
+ // When deciding whether to perform a page switch, we need to consider the most
+ // extreme X coordinate between the finger location and the center of the object
+ // being dragged. This is either the max or the min of the two depending on whether
+ // dragging to the left / right, respectively.
+ touchX = (((pageIndex < nextPage) && !mIsRtl) || (pageIndex > nextPage && mIsRtl))
+ ? Math.min(d.x, centerX) : Math.max(d.x, centerX);
+ CellLayout layout = verifyInsidePage(pageIndex, touchX, touchY);
+ if (layout != null) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the given view's bounds relative to Workspace
+ */
+ private void getViewBoundsRelativeToWorkspace(View view, Rect outRect) {
+ mLauncher.getDragLayer()
+ .getDescendantRectRelativeToSelf(view, mTempRect);
+ // map draglayer relative bounds to workspace
+ mLauncher.getDragLayer().mapRectInSelfToDescendant(this, mTempRect);
+ outRect.set(mTempRect);
+ }
+
/**
* Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
*/
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 7e9d56d..e311bc8 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -52,16 +52,7 @@
}
float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
- float shrunkBottom = grid.getWorkspaceSpringLoadShrunkBottom();
- float scale = Math.min((shrunkBottom - shrunkTop) / ws.getNormalChildHeight(), 1f);
-
- // Reduce scale if next pages would not be visible after scaling the workspace
- float scaledWorkspaceWidth = ws.getWidth() * scale;
- float maxAvailableWidth =
- ws.getWidth() - (2 * grid.getWorkspaceSpringLoadedMinimumNextPageVisible());
- if (scaledWorkspaceWidth > maxAvailableWidth) {
- scale *= maxAvailableWidth / scaledWorkspaceWidth;
- }
+ float scale = grid.getWorkspaceSpringLoadScale();
float halfHeight = ws.getHeight() / 2;
float myCenter = ws.getTop() + halfHeight;
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8005181..777da23 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -242,7 +242,9 @@
change |= CHANGE_SUPPORTED_BOUNDS;
Point currentS = newInfo.currentSize;
- Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size;
+ Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
+ oldInfo.mPerDisplayBounds.get(newInfo.displayId);
+ Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
Log.e("b/198965093",
"Inconsistent number of displays"
@@ -250,10 +252,12 @@
+ "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
+ "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
}
- if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
+ if (expectedS != null
+ && (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");
+ Log.e("b/198965093",
+ "Display size changed while display is off, ignoring change");
return;
}
}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 5aaa275..61b7fa1 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -22,7 +22,6 @@
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;
@@ -157,16 +156,16 @@
int bottomNav = isTablet
? 0
: (config.screenHeightDp > config.screenWidthDp
- ? getDimenByName(NAVBAR_HEIGHT, systemRes, 0)
+ ? getDimenByName(NAVBAR_HEIGHT, systemRes)
: (isGesture
- ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0)
+ ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes)
: 0));
Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
- int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+ int statusBarHeight = getDimenByName("status_bar_height", systemRes);
Insets newStatusBarInsets = Insets.of(
statusBarInsets.left,
Math.max(statusBarInsets.top, statusBarHeight),
@@ -222,23 +221,23 @@
boolean isTabletOrGesture = isTablet
|| (Utilities.ATLEAST_R && isGestureNav(context));
- int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+ int statusBarHeight = getDimenByName("status_bar_height", systemRes);
int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
navBarHeightPortrait = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
- : getDimenByName(NAVBAR_HEIGHT, systemRes, 0);
+ : getDimenByName(NAVBAR_HEIGHT, systemRes);
navBarHeightLandscape = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
: (isTabletOrGesture
- ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0);
+ ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes) : 0);
navbarWidthLandscape = isTabletOrGesture
? 0
- : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0);
+ : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes);
WindowBounds[] result = new WindowBounds[4];
Point tempSize = new Point();
@@ -274,6 +273,13 @@
return result;
}
+ /**
+ * Wrapper around the utility method for easier emulation
+ */
+ protected int getDimenByName(String resName, Resources res) {
+ return ResourceUtils.getDimenByName(resName, res, 0);
+ }
+
protected boolean isGestureNav(Context context) {
return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f71aa13..4c001fd 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -413,6 +413,14 @@
}
/**
+ * Similar to {@link #mapCoordInSelfToDescendant(View descendant, float[] coord)}
+ * but accepts a Rect instead of float[].
+ */
+ public void mapRectInSelfToDescendant(View descendant, Rect rect) {
+ Utilities.mapRectInSelfToDescendant(descendant, this, rect);
+ }
+
+ /**
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
*/
public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
diff --git a/tests/res/raw/devices.json b/tests/res/raw/devices.json
new file mode 100644
index 0000000..a78dd86
--- /dev/null
+++ b/tests/res/raw/devices.json
@@ -0,0 +1,45 @@
+{
+ "pixel6pro": {
+ "width": 1440,
+ "height": 3120,
+ "density": 560,
+ "name": "pixel6pro",
+ "cutout": "0, 130, 0, 0",
+ "grids": [
+ "normal",
+ "reasonable",
+ "practical",
+ "big",
+ "crazy_big"
+ ],
+ "resourceOverrides": {
+ "status_bar_height": 98,
+ "navigation_bar_height_landscape": 56,
+ "navigation_bar_height": 56,
+ "navigation_bar_width": 56
+ }
+ },
+ "test": {
+ "data needs updating": 0
+ },
+ "pixel5": {
+ "width": 1080,
+ "height": 2340,
+ "density": 440,
+ "name": "pixel5",
+ "cutout": "0, 136, 0, 0",
+ "grids": [
+ "normal",
+ "reasonable",
+ "practical",
+ "big",
+ "crazy_big"
+ ],
+ "resourceOverrides": {
+ "status_bar_height": 66,
+ "navigation_bar_height_landscape": 44,
+ "navigation_bar_height": 44,
+ "navigation_bar_width": 44
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
new file mode 100644
index 0000000..31468c5
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.deviceemulator;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+import java.util.concurrent.Callable;
+
+
+public class DisplayEmulator {
+ Context mContext;
+ LauncherInstrumentation mLauncher;
+ DisplayEmulator(Context context, LauncherInstrumentation launcher) {
+ mContext = context;
+ mLauncher = launcher;
+ }
+
+ /**
+ * By changing the WindowManagerProxy we can override the window insets information
+ **/
+ private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
+ WindowManagerProxy.INSTANCE.initializeForTesting(
+ new TestWindowManagerProxy(mContext, deviceData));
+ return WindowManagerGlobal.getWindowManagerService();
+ }
+
+ public <T> T emulate(DeviceEmulationData device, String grid, Callable<T> runInEmulation)
+ throws Exception {
+ WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
+ // Set up emulation
+ final int userId = UserHandle.myUserId();
+ WindowManagerProxy.INSTANCE.initializeForTesting(
+ new TestWindowManagerProxy(mContext, device));
+ IWindowManager wm = changeWindowManagerInstance(device);
+ // Change density twice to force display controller to reset its state
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density, userId);
+ wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, device.width, device.height);
+ wm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1);
+
+ // Set up grid
+ setGrid(grid);
+ try {
+ return runInEmulation.call();
+ } finally {
+ // Clear emulation
+ WindowManagerProxy.INSTANCE.initializeForTesting(original);
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand("cmd window reset");
+ }
+ }
+
+ private void setGrid(String gridType) {
+ // When the grid changes, the desktop arrangement get stored in SQL and we need to wait to
+ // make sure there is no SQL operations running and get SQL_BUSY error, that's why we need
+ // to call mLauncher.waitForLauncherInitialized();
+ mLauncher.waitForLauncherInitialized();
+ String testProviderAuthority = mContext.getPackageName() + ".grid_control";
+ Uri gridUri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(testProviderAuthority)
+ .appendPath("default_grid")
+ .build();
+ ContentValues values = new ContentValues();
+ values.put("name", gridType);
+ mContext.getContentResolver().update(gridUri, values, null, null);
+ }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
new file mode 100644
index 0000000..ca2f81e
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.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.deviceemulator;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.WindowInsets;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+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;
+
+public class TestWindowManagerProxy extends WindowManagerProxy {
+
+ private final DeviceEmulationData mDevice;
+
+ public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
+ super(true);
+ mDevice = device;
+ }
+
+ @Override
+ public boolean isInternalDisplay(Display display) {
+ return display.getDisplayId() == Display.DEFAULT_DISPLAY;
+ }
+
+ @Override
+ protected int getDimenByName(String resName, Resources res) {
+ Integer mock = mDevice.resourceOverrides.get(resName);
+ return mock != null ? mock : super.getDimenByName(resName, res);
+ }
+
+ @Override
+ public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
+ int rotation = display.getRotation();
+ Point size = new Point(mDevice.width, mDevice.height);
+ RotationUtils.rotateSize(size, rotation);
+ Rect cutout = new Rect(mDevice.cutout);
+ RotationUtils.rotateRect(cutout, rotation);
+ return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
+ }
+
+ @Override
+ public WindowBounds getRealBounds(Context windowContext, Display display,
+ CachedDisplayInfo info) {
+ return estimateInternalDisplayBounds(windowContext)
+ .get(getDisplayId(display)).second[display.getRotation()];
+ }
+
+ @Override
+ public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
+ Rect outInsets) {
+ outInsets.set(getRealBounds(context, context.getDisplay(),
+ getDisplayInfo(context, context.getDisplay())).insets);
+ return oldInsets;
+ }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
new file mode 100644
index 0000000..3623513
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
@@ -0,0 +1,154 @@
+/*
+ * 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.deviceemulator.models;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+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 android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.ArrayMap;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntArray;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Map;
+
+public class DeviceEmulationData {
+
+ public final int width;
+ public final int height;
+ public final int density;
+ public final String name;
+ public final String[] grids;
+ public final Rect cutout;
+ public final Map<String, Integer> resourceOverrides;
+
+ private static final String[] EMULATED_SYSTEM_RESOURCES = new String[]{
+ NAVBAR_HEIGHT,
+ NAVBAR_HEIGHT_LANDSCAPE,
+ NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE,
+ "status_bar_height",
+ };
+
+ public DeviceEmulationData(int width, int height, int density, Rect cutout, String name,
+ String[] grid,
+ Map<String, Integer> resourceOverrides) {
+ this.width = width;
+ this.height = height;
+ this.density = density;
+ this.name = name;
+ this.grids = grid;
+ this.cutout = cutout;
+ this.resourceOverrides = resourceOverrides;
+ }
+
+ public static DeviceEmulationData deviceFromJSON(JSONObject json) throws JSONException {
+ int width = json.getInt("width");
+ int height = json.getInt("height");
+ int density = json.getInt("density");
+ String name = json.getString("name");
+
+ JSONArray gridArray = json.getJSONArray("grids");
+ String[] grids = new String[gridArray.length()];
+ for (int i = 0, count = grids.length; i < count; i++) {
+ grids[i] = gridArray.getString(i);
+ }
+
+ IntArray deviceCutout = IntArray.fromConcatString(json.getString("cutout"));
+ Rect cutout = new Rect(deviceCutout.get(0), deviceCutout.get(1), deviceCutout.get(2),
+ deviceCutout.get(3));
+
+
+ JSONObject resourceOverridesJson = json.getJSONObject("resourceOverrides");
+ Map<String, Integer> resourceOverrides = new ArrayMap<>();
+ for (String key : resourceOverridesJson.keySet()) {
+ resourceOverrides.put(key, resourceOverridesJson.getInt(key));
+ }
+ return new DeviceEmulationData(width, height, density, cutout, name, grids,
+ resourceOverrides);
+ }
+
+ @Override
+ public String toString() {
+ JSONObject json = new JSONObject();
+ try {
+ json.put("width", width);
+ json.put("height", height);
+ json.put("density", density);
+ json.put("name", name);
+ json.put("cutout", IntArray.wrap(
+ cutout.left, cutout.top, cutout.right, cutout.bottom).toConcatString());
+
+ JSONArray gridArray = new JSONArray();
+ Arrays.stream(grids).forEach(gridArray::put);
+ json.put("grids", gridArray);
+
+
+ JSONObject resourceOverrides = new JSONObject();
+ for (Map.Entry<String, Integer> e : this.resourceOverrides.entrySet()) {
+ resourceOverrides.put(e.getKey(), e.getValue());
+ }
+ json.put("resourceOverrides", resourceOverrides);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return json.toString();
+ }
+
+ public static DeviceEmulationData getCurrentDeviceData(Context context) {
+ DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+ String[] grids = InvariantDeviceProfile.INSTANCE.get(context)
+ .parseAllGridOptions(context).stream()
+ .map(go -> go.name).toArray(String[]::new);
+ String code = Build.MODEL.replaceAll("\\s", "").toLowerCase();
+
+ Map<String, Integer> resourceOverrides = new ArrayMap<>();
+ for (String s : EMULATED_SYSTEM_RESOURCES) {
+ resourceOverrides.put(s, getDimenByName(s, context.getResources(), 0));
+ }
+ return new DeviceEmulationData(info.currentSize.x, info.currentSize.y,
+ info.densityDpi, info.cutout, code, grids, resourceOverrides);
+ }
+
+ public static DeviceEmulationData getDevice(String deviceCode) throws Exception {
+ return DeviceEmulationData.deviceFromJSON(readJSON().getJSONObject(deviceCode));
+ }
+
+ private static JSONObject readJSON() throws Exception {
+ Context context = getInstrumentation().getContext();
+ Resources myRes = context.getResources();
+ int resId = myRes.getIdentifier("devices", "raw", context.getPackageName());
+ try (InputStream is = myRes.openRawResource(resId)) {
+ return new JSONObject(new String(IOUtils.toByteArray(is)));
+ }
+ }
+
+}