Merge "Add back screenshot button to Launcher3" into sc-v2-dev
diff --git a/Android.bp b/Android.bp
index 60ef5b1..bab994a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -258,6 +258,10 @@
"go/res",
"go/quickstep/res",
],
+ // Note the ordering here is important when it comes to resource
+ // overriding. We want the most specific resource overrides defined
+ // in QuickstepResLib to take precendece, so it should be the final
+ // dependency. See b/205278434 for how this can go wrong.
static_libs: [
"Launcher3CommonDepsLib",
"QuickstepResLib",
@@ -283,11 +287,15 @@
libs: [
"framework-statsd",
],
+ // Note the ordering here is important when it comes to resource
+ // overriding. We want the most specific resource overrides defined
+ // in QuickstepResLib to take precendece, so it should be the final
+ // dependency. See b/208647810 for how this can go wrong.
static_libs: [
- "QuickstepResLib",
"SystemUI-statsd",
"SystemUISharedLib",
"Launcher3CommonDepsLib",
+ "QuickstepResLib",
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 151b8e4..a4bbae0 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -218,10 +218,10 @@
<string name="taskbar_edu_closed">Taskbar education closed</string>
<!-- Text in dialog that lets a user know how they can use the taskbar to switch apps on their device.
[CHAR_LIMIT=60] -->
- <string name="taskbar_edu_switch_apps" translatable="false">Use the taskbar to switch apps</string>
+ <string name="taskbar_edu_switch_apps">Use the taskbar to switch apps</string>
<!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device.
[CHAR_LIMIT=60] -->
- <string name="taskbar_edu_splitscreen" translatable="false">Drag to the side to use two apps at once</string>
+ <string name="taskbar_edu_splitscreen">Drag to the side to use two apps at once</string>
<!-- Text in dialog that lets a user know how they can hide the taskbar on their device.
[CHAR_LIMIT=60] -->
<string name="taskbar_edu_stashing">Touch & hold to hide the taskbar</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 319833d..ce1e8b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -233,14 +233,6 @@
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
}
- public void onDestroy() {
- mPropertyHolders.clear();
- mControllers.rotationButtonController.unregisterListeners();
- if (mFloatingRotationButton != null) {
- mFloatingRotationButton.hide();
- }
- }
-
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
TaskbarNavButtonController navButtonController) {
@@ -466,6 +458,14 @@
}
}
+ public void onDestroy() {
+ mPropertyHolders.clear();
+ mControllers.rotationButtonController.unregisterListeners();
+ if (mFloatingRotationButton != null) {
+ mFloatingRotationButton.hide();
+ }
+ }
+
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 8a30aad..5541a46 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -115,8 +115,6 @@
}
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
- final ResourceProvider rp = DynamicResource.provider(mActivity);
- final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */);
@@ -127,19 +125,15 @@
return new FloatingViewHomeAnimationFactory(floatingIconView) {
- // There is a delay in loading the icon, so we need to keep the window
- // opaque until it is ready.
- private boolean mIsFloatingIconReady = false;
-
@Nullable
@Override
protected View getViewIgnoredInWorkspaceRevealAnimation() {
return workspaceView;
}
+ @NonNull
@Override
public RectF getWindowTargetRect() {
- super.getWindowTargetRect();
return iconLocation;
}
@@ -152,15 +146,6 @@
}
@Override
- public boolean keepWindowOpaque() {
- if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
- mIsFloatingIconReady = true;
- return false;
- }
- return true;
- }
-
- @Override
public void update(RectF currentRect, float progress, float radius) {
super.update(currentRect, progress, radius);
floatingIconView.update(1f /* alpha */, 255 /* fgAlpha */, currentRect, progress,
@@ -215,11 +200,6 @@
}
@Override
- public boolean keepWindowOpaque() {
- return false;
- }
-
- @Override
public void update(RectF currentRect, float progress, float radius) {
super.update(currentRect, progress, radius);
final float fallbackBackgroundAlpha =
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index c5f4a53..097850f 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
@@ -219,6 +220,26 @@
return newTasks;
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentTasksList:");
+ writer.println(prefix + " mChangeId=" + mChangeId);
+ writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
+ for (GroupTask task : mResultsUi) {
+ writer.println(prefix + " t1=" + task.task1.key.id
+ + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
+ }
+ writer.println(prefix + " ]");
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ ArrayList<GroupedRecentTaskInfo> rawTasks =
+ mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ writer.println(prefix + " rawTasks=[");
+ for (GroupedRecentTaskInfo task : rawTasks) {
+ writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
+ + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
+ }
+ writer.println(prefix + " ]");
+ }
+
private static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e539a8c..5d77a6e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -43,6 +43,7 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -220,6 +221,11 @@
mThumbnailChangeListeners.remove(listener);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentsModel:");
+ mTaskList.dump(" ", writer);
+ }
+
/**
* Listener for receiving various task properties changes
*/
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index b36cb0a..8e9b668 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -183,8 +183,6 @@
public void setAnimation(RectFSpringAnim anim) { }
- public boolean keepWindowOpaque() { return false; }
-
public void update(RectF currentRect, float progress, float radius) { }
public void onCancel() { }
@@ -338,9 +336,6 @@
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float alpha = mAnimationFactory.getWindowAlpha(progress);
- if (mAnimationFactory.keepWindowOpaque()) {
- alpha = 1f;
- }
mLocalTransformParams
.setTargetAlpha(alpha)
.setCornerRadius(cornerRadius);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 6c623bc..67e7f88 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -83,14 +83,16 @@
MAIN_EXECUTOR.execute(() -> clearProxy());
};
- // Save the listeners passed into the proxy since when set/register these listeners,
- // setProxy may not have been called, eg. OverviewProxyService is not connected yet.
- private IPipAnimationListener mPendingPipAnimationListener;
- private ISplitScreenListener mPendingSplitScreenListener;
- private IStartingWindowListener mPendingStartingWindowListener;
- private ISmartspaceCallback mPendingSmartspaceCallback;
- private IRecentTasksListener mPendingRecentTasksListener;
- private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();
+ // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+ // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
+ // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+ // in case SysUI needs to rebind.
+ private IPipAnimationListener mPipAnimationListener;
+ private ISplitScreenListener mSplitScreenListener;
+ private IStartingWindowListener mStartingWindowListener;
+ private ISmartspaceCallback mSmartspaceCallback;
+ private IRecentTasksListener mRecentTasksListener;
+ private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -167,29 +169,23 @@
mRecentTasks = recentTasks;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
- if (mPendingPipAnimationListener != null && mPip != null) {
- setPinnedStackAnimationListener(mPendingPipAnimationListener);
- mPendingPipAnimationListener = null;
+ if (mPipAnimationListener != null && mPip != null) {
+ setPinnedStackAnimationListener(mPipAnimationListener);
}
- if (mPendingSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mPendingSplitScreenListener);
- mPendingSplitScreenListener = null;
+ if (mSplitScreenListener != null && mSplitScreen != null) {
+ registerSplitScreenListener(mSplitScreenListener);
}
- if (mPendingStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mPendingStartingWindowListener);
- mPendingStartingWindowListener = null;
+ if (mStartingWindowListener != null && mStartingWindow != null) {
+ setStartingWindowListener(mStartingWindowListener);
}
- if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
- setSmartspaceCallback(mPendingSmartspaceCallback);
- mPendingSmartspaceCallback = null;
+ if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
+ setSmartspaceCallback(mSmartspaceCallback);
}
- for (int i = mPendingRemoteTransitions.size() - 1; i >= 0; --i) {
- registerRemoteTransition(mPendingRemoteTransitions.get(i));
+ for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
+ registerRemoteTransition(mRemoteTransitions.get(i));
}
- mPendingRemoteTransitions.clear();
- if (mPendingRecentTasksListener != null && mRecentTasks != null) {
- registerRecentTasksListener(mPendingRecentTasksListener);
- mPendingRecentTasksListener = null;
+ if (mRecentTasksListener != null && mRecentTasks != null) {
+ registerRecentTasksListener(mRecentTasksListener);
}
if (mPendingSetNavButtonAlpha != null) {
@@ -513,9 +509,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
}
- } else {
- mPendingPipAnimationListener = listener;
}
+ mPipAnimationListener = listener;
}
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
@@ -553,9 +548,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
- } else {
- mPendingSplitScreenListener = listener;
}
+ mSplitScreenListener = listener;
}
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
@@ -566,7 +560,7 @@
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
- mPendingSplitScreenListener = null;
+ mSplitScreenListener = null;
}
/** Start multiple tasks in split-screen simultaneously. */
@@ -687,8 +681,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRemoteTransition");
}
- } else {
- mPendingRemoteTransitions.add(remoteTransition);
+ }
+ if (!mRemoteTransitions.contains(remoteTransition)) {
+ mRemoteTransitions.add(remoteTransition);
}
}
@@ -700,7 +695,7 @@
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
- mPendingRemoteTransitions.remove(remoteTransition);
+ mRemoteTransitions.remove(remoteTransition);
}
//
@@ -717,9 +712,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingStartingWindowListener = listener;
}
+ mStartingWindowListener = listener;
}
//
@@ -733,9 +727,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingSmartspaceCallback = callback;
}
+ mSmartspaceCallback = callback;
}
//
@@ -749,9 +742,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRecentTasksListener", e);
}
- } else {
- mPendingRecentTasksListener = listener;
}
+ mRecentTasksListener = listener;
}
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
@@ -762,7 +754,7 @@
Log.w(TAG, "Failed call unregisterRecentTasksListener");
}
}
- mPendingRecentTasksListener = null;
+ mRecentTasksListener = null;
}
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2c7fd71..f6f2cf9 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -973,6 +973,7 @@
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
+ RecentsModel.INSTANCE.get(this).dump("", pw);
pw.println("ProtoTrace:");
pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 9311261..b215ef1 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -249,7 +249,7 @@
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
getPagedOrientationHandler().setSplitIconParams(mIconView, mIconView2,
- taskIconHeight, mSnapshotView.getWidth(), mSnapshotView.getHeight(),
+ taskIconHeight, mSnapshotView.getMeasuredWidth(), mSnapshotView.getMeasuredHeight(),
isRtl, deviceProfile, mSplitBoundsConfig);
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index fcc6272..0294828 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.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -35,7 +33,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.SysUINavigationMode;
@@ -155,7 +152,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
- updatePaddingAndTranslations();
+ updateHorizontalPadding();
}
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -198,37 +195,8 @@
return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
}
- /**
- * Aligns OverviewActionsView vertically with and offsets horizontal position based on
- * 3 button nav container in taskbar.
- */
- private void updatePaddingAndTranslations() {
- boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
- SysUINavigationMode.getMode(getContext()) == THREE_BUTTONS;
- if (alignFor3ButtonTaskbar) {
- // Add extra horizontal spacing
- int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
- if (isLayoutRtl()) {
- setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0);
- } else {
- setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0);
- }
-
- // Align vertically, using taskbar height + mDp.taskbarOffsetY() to guestimate
- // where the button nav top is
- View startActionView = findViewById(R.id.action_screenshot);
- int marginBottom = getOverviewActionsBottomMarginPx(
- SysUINavigationMode.getMode(getContext()), mDp);
- int actionsTop = (mDp.heightPx - marginBottom - mInsets.bottom);
- int navTop = mDp.heightPx - (mDp.taskbarSize + mDp.getTaskbarOffsetY());
- int transY = navTop - actionsTop
- + ((mDp.taskbarSize - startActionView.getHeight()) / 2);
- setTranslationY(transY);
- } else {
- setPadding(mInsets.left, 0, mInsets.right, 0);
- setTranslationX(0);
- setTranslationY(0);
- }
+ private void updateHorizontalPadding() {
+ setPadding(mInsets.left, 0, mInsets.right, 0);
}
/** Updates vertical margins for different navigation mode or configuration changes. */
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 8c98c38..2ad586d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1148,7 +1148,7 @@
for (int i = 0; i < taskCount; i++) {
View v = getTaskViewAt(i);
if (!(v instanceof GroupedTaskView)) {
- continue;
+ return;
}
GroupedTaskView gtv = (GroupedTaskView) v;
gtv.onTaskListVisibilityChanged(false);
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3efaddc..f3c2143 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -300,6 +300,7 @@
<dimen name="snackbar_elevation">3dp</dimen>
<dimen name="snackbar_min_text_size">12sp</dimen>
<dimen name="snackbar_max_text_size">14sp</dimen>
+ <dimen name="snackbar_max_width">504dp</dimen>
<!-- Developer Options -->
<dimen name="developer_options_filter_margins">10dp</dimen>
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index b9f1b66..cb1ba7d 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -541,17 +541,18 @@
if (deviceProfile.isLandscape) {
primaryIconParams.gravity = TOP | START;
- primaryIconView.setTranslationX(primarySnapshotWidth - primaryIconView.getWidth());
+ primaryIconView.setTranslationX(
+ primarySnapshotWidth - primaryIconView.getMeasuredWidth());
primaryIconView.setTranslationY(0);
secondaryIconParams.gravity = TOP | START;
secondaryIconView.setTranslationX(primarySnapshotWidth + dividerBar);
} else {
primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- primaryIconView.setTranslationX(-(primaryIconView.getWidth()) / 2f);
+ primaryIconView.setTranslationX(-(primaryIconView.getMeasuredWidth()) / 2f);
primaryIconView.setTranslationY(0);
secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- secondaryIconView.setTranslationX(secondaryIconView.getWidth() / 2f);
+ secondaryIconView.setTranslationX(secondaryIconView.getMeasuredWidth() / 2f);
}
secondaryIconView.setTranslationY(0);
primaryIconView.setLayoutParams(primaryIconParams);
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index f945819..e582114 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -88,9 +88,14 @@
int maxMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_max_margin_left_right);
int minMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_min_margin_left_right);
int marginBottom = res.getDimensionPixelSize(R.dimen.snackbar_margin_bottom);
+ int absoluteMaxWidth = res.getDimensionPixelSize(R.dimen.snackbar_max_width);
Rect insets = activity.getDeviceProfile().getInsets();
- int maxWidth = dragLayer.getWidth() - minMarginLeftRight * 2 - insets.left - insets.right;
- int minWidth = dragLayer.getWidth() - maxMarginLeftRight * 2 - insets.left - insets.right;
+ int maxWidth = Math.min(
+ dragLayer.getWidth() - minMarginLeftRight * 2 - insets.left - insets.right,
+ absoluteMaxWidth);
+ int minWidth = Math.min(
+ dragLayer.getWidth() - maxMarginLeftRight * 2 - insets.left - insets.right,
+ absoluteMaxWidth);
params.width = minWidth;
params.setMargins(0, 0, 0, marginBottom + insets.bottom);
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c90d283..41c7c37 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -80,12 +80,8 @@
assertTrue(message, failed);
}
- private int pagesPerScreen() {
- return mLauncher.isTwoPanels() ? 2 : 1;
- }
-
- private boolean isWorkspaceScrollable(Launcher launcher) {
- return launcher.getWorkspace().getPageCount() > pagesPerScreen();
+ public static boolean isWorkspaceScrollable(Launcher launcher) {
+ return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
}
private int getCurrentWorkspacePage(Launcher launcher) {
@@ -192,7 +188,7 @@
executeOnLauncher(
launcher -> assertEquals(
"Ensuring workspace scrollable didn't switch to next screen",
- pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+ workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
executeOnLauncher(
launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
isWorkspaceScrollable(launcher)));
@@ -209,7 +205,7 @@
workspace.flingForward();
executeOnLauncher(
launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
- pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+ workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
// Test starting a workspace app.
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
new file mode 100644
index 0000000..b048cd4
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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.ui.workspace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for two panel workspace.
+ *
+ * Note running these tests will clear the workspace on the device.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TwoPanelWorkspaceTest extends AbstractLauncherUiTest {
+
+ Workspace mWorkspace;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ TaplTestsLauncher3.initialize(this);
+ mWorkspace = mLauncher.getWorkspace();
+ }
+
+ @Test
+ public void testDragIconToRightPanel() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Chrome"), 1);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Maps", "Play Store");
+ assertItemsOnPage(launcher, 1, "Chrome");
+ });
+ }
+
+ @Test
+ public void testDragIconToPage2() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertPageEmpty(launcher, 1);
+ assertItemsOnPage(launcher, 2, "Maps");
+ assertPageEmpty(launcher, 3);
+ });
+ }
+
+ @Test
+ public void testDragIconToPage3() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Phone"), 3);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ assertPageEmpty(launcher, 2);
+ assertItemsOnPage(launcher, 3, "Phone");
+ });
+ }
+
+
+ @Test
+ public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 3);
+ mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Chrome"), 0);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertPageEmpty(launcher, 1);
+ assertItemsOnPage(launcher, 2, "Chrome");
+ assertItemsOnPage(launcher, 3, "Maps");
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), -1);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertItemsOnPage(launcher, 1, "Maps");
+ assertItemsOnPage(launcher, 2, "Chrome");
+ assertPageEmpty(launcher, 3);
+ });
+
+ // Move Chrome to the right panel as well, to make sure pages are not deleted whichever
+ // page is the empty one
+ mWorkspace.flingForward();
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Chrome"), 1);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertItemsOnPage(launcher, 1, "Maps");
+ assertPageEmpty(launcher, 2);
+ assertItemsOnPage(launcher, 3, "Chrome");
+ });
+ }
+
+
+ @Test
+ public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Play Store"), 2);
+ mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Camera"), 1);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3);
+ assertItemsOnPage(launcher, 0, "Maps");
+ assertPageEmpty(launcher, 1);
+ assertItemsOnPage(launcher, 2, "Play Store");
+ assertItemsOnPage(launcher, 3, "Camera");
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Camera"), -1);
+ mWorkspace.flingForward();
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Play Store"), -2);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertItemsOnPage(launcher, 1, "Camera");
+ });
+ }
+
+ @Test
+ public void testMiddleEmptyPagesGetRemoved() {
+ if (!mLauncher.isTwoPanels()) {
+ return;
+ }
+
+ // Pre verifying the screens
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1);
+ assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+ assertPageEmpty(launcher, 1);
+ });
+
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+ mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Messages"), 3);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 2, 3, 4, 5);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertPageEmpty(launcher, 1);
+ assertItemsOnPage(launcher, 2, "Maps");
+ assertPageEmpty(launcher, 3);
+ assertPageEmpty(launcher, 4);
+ assertItemsOnPage(launcher, 5, "Messages");
+ });
+
+ mWorkspace.flingBackward();
+ mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+
+ executeOnLauncher(launcher -> {
+ assertPagesExist(launcher, 0, 1, 4, 5);
+ assertItemsOnPage(launcher, 0, "Play Store");
+ assertPageEmpty(launcher, 1);
+ assertItemsOnPage(launcher, 4, "Maps");
+ assertItemsOnPage(launcher, 5, "Messages");
+ });
+ }
+
+ private void assertPageEmpty(Launcher launcher, int pageId) {
+ CellLayout page = launcher.getWorkspace().getScreenWithId(pageId);
+ assertNotNull("Page " + pageId + " does NOT exist.", page);
+ assertEquals("Page " + pageId + " is NOT empty. Number of items on the page:", 0,
+ page.getShortcutsAndWidgets().getChildCount());
+ }
+
+ private void assertPagesExist(Launcher launcher, int... pageIds) {
+ int pageCount = launcher.getWorkspace().getPageCount();
+ assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
+ for (int i = 0; i < pageCount; i++) {
+ CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
+ int pageId = launcher.getWorkspace().getIdForScreen(page);
+ assertEquals("The page's id at index " + i + " does NOT match.", pageId,
+ pageIds[i]);
+ }
+ }
+
+ private void assertItemsOnPage(Launcher launcher, int pageId, String... itemTitles) {
+ Set<String> itemTitleSet = Arrays.stream(itemTitles).collect(Collectors.toSet());
+ CellLayout page = launcher.getWorkspace().getScreenWithId(pageId);
+ int itemCount = page.getShortcutsAndWidgets().getChildCount();
+ for (int i = 0; i < itemCount; i++) {
+ ItemInfo itemInfo = (ItemInfo) page.getShortcutsAndWidgets().getChildAt(i).getTag();
+ if (itemInfo != null) {
+ assertTrue("There was an extra item on page " + pageId + ": " + itemInfo.title,
+ itemTitleSet.remove(itemInfo.title));
+ }
+ }
+ assertTrue("Could NOT find some of the items on page " + pageId + ": "
+ + itemTitleSet.stream().collect(Collectors.joining(",")),
+ itemTitleSet.isEmpty());
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 0145690..d9f5cc8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -145,16 +145,7 @@
if (!isWorkspaceScrollable(workspace)) {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dragging icon to a second page of workspace to make it scrollable")) {
- dragIconToWorkspace(
- mLauncher,
- getHotseatAppIcon("Chrome"),
- new Point(mLauncher.getDevice().getDisplayWidth(),
- mLauncher.getVisibleBounds(workspace).centerY()),
- "popup_container",
- false,
- false,
- () -> mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
+ dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
verifyActiveContainer();
}
}
@@ -163,6 +154,48 @@
}
}
+ /**
+ * Returns the number of pages that are visible on the screen simultaneously.
+ */
+ public int pagesPerScreen() {
+ return mLauncher.isTwoPanels() ? 2 : 1;
+ }
+
+ /**
+ * Drags an icon to the (currentPage + pageDelta) page if the page already exists.
+ * If the target page doesn't exist, the icon will be put onto an existing page that is the
+ * closest to the target page.
+ *
+ * @param appIcon - icon to drag.
+ * @param pageDelta - how many pages should the icon be dragged from the current page.
+ * It can be a negative value.
+ */
+ public void dragIcon(AppIcon appIcon, int pageDelta) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ final UiObject2 workspace = verifyActiveContainer();
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "dragging icon to page with delta: " + pageDelta)) {
+ dragIcon(workspace, appIcon, pageDelta);
+ verifyActiveContainer();
+ }
+ }
+ }
+
+ private void dragIcon(UiObject2 workspace, AppIcon appIcon, int pageDelta) {
+ int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
+ int targetX = (pageWidth / 2) + pageWidth * pageDelta;
+ dragIconToWorkspace(
+ mLauncher,
+ appIcon,
+ new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
+ "popup_container",
+ false,
+ false,
+ () -> mLauncher.expectEvent(
+ TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
+ verifyActiveContainer();
+ }
+
private boolean isWorkspaceScrollable(UiObject2 workspace) {
return workspace.getChildCount() > (mLauncher.isTwoPanels() ? 2 : 1);
}
@@ -258,10 +291,26 @@
try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
"want to drag icon to workspace")) {
final long downTime = SystemClock.uptimeMillis();
- final Point dragStartCenter = dragIconToSpringLoaded(launcher, downTime,
+ Point dragStart = dragIconToSpringLoaded(launcher, downTime,
launchable.getObject(), longPressIndicator, expectLongClickEvents);
- final Point targetDest = dest.get();
- launcher.movePointer(dragStartCenter, targetDest, 10, downTime, true,
+ Point targetDest = dest.get();
+ int displayX = launcher.getRealDisplaySize().x;
+
+ // Since the destination can be on another page, we need to drag to the edge first
+ // until we reach the target page
+ while (targetDest.x > displayX || targetDest.x < 0) {
+ int edgeX = targetDest.x > 0 ? displayX : 0;
+ Point screenEdge = new Point(edgeX, targetDest.y);
+ launcher.movePointer(dragStart, screenEdge, 10, downTime, true,
+ LauncherInstrumentation.GestureScope.INSIDE);
+ launcher.waitForIdle(); // Wait for the page change to happen
+ targetDest.x += displayX * (targetDest.x > 0 ? -1 : 1);
+ dragStart = screenEdge;
+ }
+
+ // targetDest.x is now between 0 and displayX so we found the target page,
+ // we just have to put move the icon to the destination and drop it
+ launcher.movePointer(dragStart, targetDest, 10, downTime, true,
LauncherInstrumentation.GestureScope.INSIDE);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
}