Merge "Fix taskbar jumping when going home" 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/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
index d97eae1..9ee6c44 100644
--- a/quickstep/res/drawable/ic_screenshot.xml
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -13,11 +13,20 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
<path
- android:fillColor="#FF000000"
- android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,21L7,21v-1h10v1zM17,18L7,18L7,6h10v12zM17,4L7,4L7,3h10v1zM9.5,8.5L12,8.5L12,7L8,7v4h1.5zM12,17h4v-4h-1.5v2.5L12,15.5z"/>
-</vector>
+ android:pathData="M5.8334,1.666H8.3334V3.3327H5.8334V6.666H4.1667V3.3327C4.1667,2.4122 4.9129,1.666 5.8334,1.666Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M4.1667,13.3327V16.666C4.1667,17.5865 4.9129,18.3327 5.8334,18.3327H8.3334V16.666H5.8334V13.3327H4.1667Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M14.1667,13.3327V16.666H11.6667V18.3327H14.1667C15.0872,18.3327 15.8334,17.5865 15.8334,16.666V13.3327H14.1667Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M15.8334,6.666V3.3327C15.8334,2.4122 15.0872,1.666 14.1667,1.666H11.6667V3.3327H14.1667V6.666H15.8334Z"
+ android:fillColor="#000000"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index dd8afc2..0fda0bf 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -38,6 +38,20 @@
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_screenshot"
android:text="@string/action_screenshot"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Space
+ android:id="@+id/action_split_space"
+ android:layout_width="@dimen/overview_actions_button_spacing"
+ android:layout_height="1dp"
+ android:visibility="gone" />
+
+ <Button
+ android:id="@+id/action_split"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/action_split"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
@@ -46,23 +60,6 @@
android:layout_height="1dp"
android:layout_weight="1" />
- <Button
- android:id="@+id/action_split"
- style="@style/OverviewActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableStart="@drawable/ic_split_screen"
- android:text="@string/action_split"
- android:theme="@style/ThemeControlHighlightWorkspaceColor"
- android:visibility="gone" />
-
- <Space
- android:id="@+id/action_split_space"
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1"
- android:visibility="gone" />
-
<Space
android:id="@+id/oav_three_button_space"
android:layout_width="0dp"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4e6b7b9..c37b335 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -39,6 +39,8 @@
<!-- 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_button_spacing_grid">36dp</dimen>
<dimen name="overview_actions_margin_gesture">28dp</dimen>
<dimen name="overview_actions_top_margin_gesture_grid_portrait">19.37dp</dimen>
<dimen name="overview_actions_bottom_margin_gesture_grid_portrait">22dp</dimen>
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/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 51d7914..0123c4f 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1214,14 +1214,14 @@
}
/**
- * Returns view on the workspace that corresponds to the closing app in the list of app targets
+ * Returns view on launcher that corresponds to the closing app in the list of app targets
*/
- private @Nullable View findWorkspaceView(RemoteAnimationTargetCompat[] appTargets) {
+ private @Nullable View findLauncherView(RemoteAnimationTargetCompat[] appTargets) {
for (RemoteAnimationTargetCompat appTarget : appTargets) {
if (appTarget.mode == MODE_CLOSING) {
- View workspaceView = findWorkspaceView(appTarget);
- if (workspaceView != null) {
- return workspaceView;
+ View launcherView = findLauncherView(appTarget);
+ if (launcherView != null) {
+ return launcherView;
}
}
}
@@ -1229,9 +1229,9 @@
}
/**
- * Returns view on the workspace that corresponds to the {@param runningTaskTarget}.
+ * Returns view on launcher that corresponds to the {@param runningTaskTarget}.
*/
- private @Nullable View findWorkspaceView(RemoteAnimationTargetCompat runningTaskTarget) {
+ private @Nullable View findLauncherView(RemoteAnimationTargetCompat runningTaskTarget) {
if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) {
return null;
}
@@ -1269,7 +1269,7 @@
}
}
- return mLauncher.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
+ return mLauncher.getFirstMatchForAppClose(launchCookieItemId,
packageName, UserHandle.of(runningTaskTarget.taskInfo.userId));
}
@@ -1292,7 +1292,7 @@
* Closing animator that animates the window into its final location on the workspace.
*/
private void getClosingWindowAnimators(AnimatorSet animation,
- RemoteAnimationTargetCompat[] targets, View workspaceView) {
+ RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) {
FloatingIconView floatingIconView = null;
FloatingWidgetView floatingWidget = null;
RectF targetRect = new RectF();
@@ -1308,17 +1308,17 @@
}
// Get floating view and target rect.
- if (workspaceView instanceof LauncherAppWidgetHostView) {
+ if (launcherView instanceof LauncherAppWidgetHostView) {
Size windowSize = new Size(mDeviceProfile.availableWidthPx,
mDeviceProfile.availableHeightPx);
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget);
floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher,
- (LauncherAppWidgetHostView) workspaceView, targetRect, windowSize,
+ (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
isTransluscent, fallbackBackgroundColor);
- } else if (workspaceView != null) {
- floatingIconView = getFloatingIconView(mLauncher, workspaceView,
+ } else if (launcherView != null) {
+ floatingIconView = getFloatingIconView(mLauncher, launcherView,
true /* hideOriginal */, targetRect, false /* isOpening */);
} else {
targetRect.set(getDefaultWindowTargetRect());
@@ -1373,15 +1373,10 @@
}
// Use a fixed velocity to start the animation.
- float velocityPxPerS = DynamicResource.provider(mLauncher)
- .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
- PointF velocity = new PointF(0, -velocityPxPerS);
- animation.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
- true /* animateOverviewScrim */, workspaceView).getAnimators());
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- anim.start(mLauncher, velocity);
+ anim.start(mLauncher, velocityPxPerS);
}
});
}
@@ -1556,22 +1551,30 @@
if (anim == null) {
anim = new AnimatorSet();
- View workspaceView = findWorkspaceView(appTargets);
- boolean isWorkspaceViewVisible = workspaceView != null
- && !mLauncher.isInState(LauncherState.ALL_APPS)
- && !mLauncher.getWorkspace().isOverlayShown();
- boolean playFallBackAnimation = !isWorkspaceViewVisible
- && (launcherIsATargetWithMode(appTargets, MODE_OPENING)
- || mLauncher.isForceInvisible());
+ final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+ || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+ View launcherView = findLauncherView(appTargets);
+ boolean playFallBackAnimation = (launcherView == null
+ && launcherIsForceInvisibleOrOpening)
+ || mLauncher.getWorkspace().isOverlayShown();
boolean playWorkspaceReveal = true;
if (mFromUnlock) {
anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
} else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
&& !playFallBackAnimation) {
- getClosingWindowAnimators(anim, appTargets, workspaceView);
- // We play StaggeredWorkspaceAnim as a part of the closing window animation.
- playWorkspaceReveal = false;
+ // Use a fixed velocity to start the animation.
+ float velocityPxPerS = DynamicResource.provider(mLauncher)
+ .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+ PointF velocity = new PointF(0, -velocityPxPerS);
+ getClosingWindowAnimators(anim, appTargets, launcherView, velocity);
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+ true /* animateOverviewScrim */, launcherView).getAnimators());
+ // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+ playWorkspaceReveal = false;
+ }
} else {
anim.play(getFallbackClosingWindowAnimators(appTargets));
}
@@ -1584,8 +1587,7 @@
// targets list because it is already visible). In that case, we force
// invisibility on touch down, and only reset it after the animation to home
// is initialized.
- if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
- || mLauncher.isForceInvisible()) {
+ if (launcherIsForceInvisibleOrOpening) {
addCujInstrumentation(
anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
// Only register the content animation for cancellation when state changes
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index c9cbba1..1a901f1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -55,6 +55,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -96,6 +97,7 @@
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
@@ -153,6 +155,17 @@
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
private boolean mRecentsViewScrollLinked = false;
+ private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
+ new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (mActivity != activity) {
+ return;
+ }
+ mRecentsView = null;
+ mActivity = null;
+ }
+ };
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
@@ -416,6 +429,7 @@
setupRecentsViewUi();
linkRecentsViewScroll();
+ mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
return true;
}
@@ -1542,6 +1556,9 @@
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ if (mActivity != null) {
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 8a30aad..a72935b 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 =
@@ -264,7 +244,7 @@
}
}
- return mActivity.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
+ return mActivity.getFirstMatchForAppClose(launchCookieItemId,
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
}
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/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e77ec78..97fc6d7 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -316,7 +316,7 @@
mt[i] = localMt;
Matrix localMti = new Matrix();
- localMti.invert(localMt);
+ localMt.invert(localMti);
mti[i] = localMti;
}
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 b6bf59f..0294828 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -22,7 +22,10 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -80,7 +83,7 @@
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
private final MultiValueAlpha mMultiValueAlpha;
- private View mSplitButton;
+ private Button mSplitButton;
@ActionsHiddenFlags
private int mHiddenFlags;
@@ -214,7 +217,18 @@
public void setDp(DeviceProfile dp) {
mDp = dp;
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ params.weight = dp.isVerticalBarLayout() ? 1 : 0;
+ findViewById(R.id.action_split_space).setLayoutParams(params);
+
requestLayout();
+
+ mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
+ (dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical),
+ 0, 0, 0);
}
public void setSplitButtonVisible(boolean visible) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3020dd9..2ad586d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1041,6 +1041,17 @@
}
}
+ public boolean isTaskViewFullyVisible(TaskView tv) {
+ if (showAsGrid()) {
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
+ return isTaskViewFullyWithinBounds(tv, screenStart, screenEnd);
+ } else {
+ // For now, just check if it's the active task
+ return indexOfChild(tv) == getNextPage();
+ }
+ }
+
@Nullable
private TaskView getLastGridTaskView() {
return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray());
@@ -1087,6 +1098,15 @@
&& taskEnd <= end);
}
+ private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) {
+ int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
+ showAsFullscreen(), showAsGrid());
+ int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
+ showAsFullscreen()));
+ int taskEnd = taskStart + taskSize;
+ return taskStart >= start && taskEnd <= end;
+ }
+
/**
* Returns true if the task is in expected scroll position.
*
@@ -4854,6 +4874,62 @@
}
@Override
+ public boolean scrollLeft() {
+ if (!showAsGrid()) {
+ return super.scrollLeft();
+ }
+
+ int targetPage = getNextPage();
+ if (targetPage >= 0) {
+ // Find the next page that is not fully visible.
+ TaskView taskView = getTaskViewAt(targetPage);
+ while ((taskView == null || isTaskViewFullyVisible(taskView)) && targetPage - 1 >= 0) {
+ taskView = getTaskViewAt(--targetPage);
+ }
+ // Target a scroll where targetPage is on left of screen but still fully visible.
+ int lastTaskEnd = (mIsRtl
+ ? mLastComputedGridSize.left
+ : mLastComputedGridSize.right)
+ + (mIsRtl ? mPageSpacing : -mPageSpacing);
+ int normalTaskEnd = mIsRtl
+ ? mLastComputedGridTaskSize.left
+ : mLastComputedGridTaskSize.right;
+ int targetScroll = getScrollForPage(targetPage) + normalTaskEnd - lastTaskEnd;
+ // Find a page that is close to targetScroll while not over it.
+ while (targetPage - 1 >= 0
+ && (mIsRtl
+ ? getScrollForPage(targetPage - 1) < targetScroll
+ : getScrollForPage(targetPage - 1) > targetScroll)) {
+ targetPage--;
+ }
+ snapToPage(targetPage);
+ return true;
+ }
+
+ return mAllowOverScroll;
+ }
+
+ @Override
+ public boolean scrollRight() {
+ if (!showAsGrid()) {
+ return super.scrollRight();
+ }
+
+ int targetPage = getNextPage();
+ if (targetPage < getChildCount()) {
+ // Find the next page that is not fully visible.
+ TaskView taskView = getTaskViewAt(targetPage);
+ while ((taskView != null && isTaskViewFullyVisible(taskView))
+ && targetPage + 1 < getChildCount()) {
+ taskView = getTaskViewAt(++targetPage);
+ }
+ snapToPage(targetPage);
+ return true;
+ }
+ return mAllowOverScroll;
+ }
+
+ @Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
dispatchScrollChanged();
diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml
new file mode 100644
index 0000000..ee710d0
--- /dev/null
+++ b/res/drawable/ic_split_horizontal.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="16dp"
+ android:viewportWidth="20"
+ android:viewportHeight="16">
+ <path
+ android:pathData="M18,14L13,14L13,2L18,2L18,14ZM20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14ZM7,14L2,14L2,2L7,2L7,14ZM9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_split_left.xml b/res/drawable/ic_split_left.xml
new file mode 100644
index 0000000..fc9f699
--- /dev/null
+++ b/res/drawable/ic_split_left.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="16dp"
+ android:viewportWidth="20"
+ android:viewportHeight="16">
+ <path
+ android:pathData="M-0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2ZM13,2L18,2L18,14L13,14L13,2ZM11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_split_right.xml b/res/drawable/ic_split_right.xml
new file mode 100644
index 0000000..cc15622
--- /dev/null
+++ b/res/drawable/ic_split_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="16dp"
+ android:viewportWidth="20"
+ android:viewportHeight="16">
+ <path
+ android:pathData="M20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14ZM7,14L2,14L2,2L7,2L7,14ZM9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_split_top.xml b/res/drawable/ic_split_top.xml
new file mode 100644
index 0000000..f8c15bd
--- /dev/null
+++ b/res/drawable/ic_split_top.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="20dp"
+ android:viewportWidth="16"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M14,0H2C0.9,0 0,0.9 0,2V7C0,8.1 0.9,9 2,9H14C15.1,9 16,8.1 16,7V2C16,0.9 15.1,0 14,0ZM14,13V18H2V13H14ZM14,11H2C0.9,11 0,11.9 0,13V18C0,19.1 0.9,20 2,20H14C15.1,20 16,19.1 16,18V13C16,11.9 15.1,11 14,11Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_split_vertical.xml b/res/drawable/ic_split_vertical.xml
new file mode 100644
index 0000000..9bc9785
--- /dev/null
+++ b/res/drawable/ic_split_vertical.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18,4V9H6V4H18ZM18,2H6C4.9,2 4,2.9 4,4V9C4,10.1 4.9,11 6,11H18C19.1,11 20,10.1 20,9V4C20,2.9 19.1,2 18,2ZM18,15V20H6V15H18ZM18,13H6C4.9,13 4,13.9 4,15V20C4,21.1 4.9,22 6,22H18C19.1,22 20,21.1 20,20V15C20,13.9 19.1,13 18,13Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c689942..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>
@@ -331,6 +332,8 @@
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_task_margin_focused">0dp</dimen>
<dimen name="overview_task_margin_grid">0dp</dimen>
+ <dimen name="overview_actions_button_spacing">0dp</dimen>
+ <dimen name="overview_actions_button_spacing_grid">0dp</dimen>
<dimen name="overview_actions_margin_gesture">0dp</dimen>
<dimen name="overview_actions_top_margin_gesture_grid_portrait">0dp</dimen>
<dimen name="overview_actions_bottom_margin_gesture_grid_portrait">0dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ff19918..85d8247 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -187,6 +187,7 @@
public final int overviewActionsMarginThreeButtonPx;
public final int overviewActionsTopMarginGesturePx;
public final int overviewActionsBottomMarginGesturePx;
+ public final int overviewActionsButtonSpacing;
public int overviewPageSpacing;
public int overviewRowSpacing;
public int overviewGridSideMargin;
@@ -377,10 +378,14 @@
overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
R.dimen.overview_actions_bottom_margin_gesture_grid_portrait);
}
+ overviewActionsButtonSpacing = res.getDimensionPixelSize(
+ R.dimen.overview_actions_button_spacing_grid);
} else {
overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
R.dimen.overview_actions_margin_gesture);
overviewActionsBottomMarginGesturePx = overviewActionsTopMarginGesturePx;
+ overviewActionsButtonSpacing = res.getDimensionPixelSize(
+ R.dimen.overview_actions_button_spacing);
}
overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
R.dimen.overview_actions_margin_three_button);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8154168..d60cd31 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -39,6 +40,7 @@
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -55,6 +57,7 @@
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -90,6 +93,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.Log;
@@ -215,6 +219,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -2674,6 +2679,79 @@
}
}
+ /**
+ * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
+ * animation.
+ *
+ * @param preferredItemId The id of the preferred item to match to if it exists.
+ * @param packageName The package name of the app to match.
+ * @param user The user of the app to match.
+ */
+ public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
+ final ItemInfoMatcher preferredItem = (info, cn) ->
+ info != null && info.id == preferredItemId;
+ final ItemInfoMatcher packageAndUserAndApp = (info, cn) ->
+ info != null
+ && info.itemType == ITEM_TYPE_APPLICATION
+ && info.user.equals(user)
+ && info.getTargetComponent() != null
+ && TextUtils.equals(info.getTargetComponent().getPackageName(),
+ packageName);
+
+ if (isInState(LauncherState.ALL_APPS)) {
+ return getFirstMatch(Collections.singletonList(mAppsView.getActiveRecyclerView()),
+ preferredItem, packageAndUserAndApp);
+ } else {
+ List<ViewGroup> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
+ containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets());
+ mWorkspace.forEachVisiblePage(page
+ -> containers.add(((CellLayout) page).getShortcutsAndWidgets()));
+
+ // Order: Preferred item by itself or in folder, then by matching package/user
+ if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
+ return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem),
+ packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
+ } else {
+ // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
+ // FolderAdaptiveIcon as the background.
+ return getFirstMatch(containers, preferredItem, packageAndUserAndApp);
+ }
+ }
+ }
+
+ /**
+ * Finds the first view matching the ordered operators across the given viewgroups in order.
+ * @param containers List of ViewGroups to scan, in order of preference.
+ * @param operators List of operators, in order starting from best matching operator.
+ */
+ private static View getFirstMatch(Iterable<ViewGroup> containers,
+ final ItemInfoMatcher... operators) {
+ for (ItemInfoMatcher operator : operators) {
+ for (ViewGroup container : containers) {
+ View match = mapOverViewGroup(container, operator);
+ if (match != null) {
+ return match;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first view matching the operator in the given ViewGroups, or null if none.
+ * Forward iteration matters.
+ */
+ private static View mapOverViewGroup(ViewGroup container, ItemInfoMatcher op) {
+ final int itemCount = container.getChildCount();
+ for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+ View item = container.getChildAt(itemIdx);
+ if (op.matchesInfo((ItemInfo) item.getTag())) {
+ return item;
+ }
+ }
+ return null;
+ }
+
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
.setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 1ce7ebe..2c14f07 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1749,20 +1749,23 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
final boolean pagesFlipped = isPageOrderFlipped();
- int offset = (mAllowOverScroll ? 0 : 1);
- info.setScrollable(getPageCount() > offset);
- if (getCurrentPage() < getPageCount() - offset) {
+ info.setScrollable(getPageCount() > 0);
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ if (getCurrentPage() < getPageCount() - getPanelCount()
+ || (getCurrentPage() == getPageCount() - getPanelCount()
+ && primaryScroll != getScrollForPage(getPageCount() - getPanelCount()))) {
info.addAction(pagesFlipped ?
- AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD
- : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(mIsRtl ?
AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT
: AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
}
- if (getCurrentPage() >= offset) {
+ if (getCurrentPage() > 0
+ || (getCurrentPage() == 0 && primaryScroll != getScrollForPage(0))) {
info.addAction(pagesFlipped ?
- AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
- : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.addAction(mIsRtl ?
AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT
: AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);
@@ -1807,16 +1810,16 @@
} break;
case android.R.id.accessibilityActionPageRight: {
if (!mIsRtl) {
- return scrollRight();
+ return scrollRight();
} else {
- return scrollLeft();
+ return scrollLeft();
}
}
case android.R.id.accessibilityActionPageLeft: {
if (!mIsRtl) {
- return scrollLeft();
+ return scrollLeft();
} else {
- return scrollRight();
+ return scrollRight();
}
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 203df0a..281dfea 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
@@ -51,7 +50,6 @@
import android.os.Message;
import android.os.Parcelable;
import android.os.UserHandle;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -3146,62 +3144,6 @@
return layouts;
}
- /**
- * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
- * animation.
- *
- * @param preferredItemId The id of the preferred item to match to if it exists.
- * @param packageName The package name of the app to match.
- * @param user The user of the app to match.
- */
- public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
- final ItemOperator preferredItem = (ItemInfo info, View view) ->
- info != null && info.id == preferredItemId;
- final ItemOperator preferredItemInFolder = (info, view) -> {
- if (info instanceof FolderInfo) {
- FolderInfo folderInfo = (FolderInfo) info;
- for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
- if (preferredItem.evaluate(shortcutInfo, view)) {
- return true;
- }
- }
- }
- return false;
- };
- final ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
- info != null
- && info.itemType == ITEM_TYPE_APPLICATION
- && info.user.equals(user)
- && info.getTargetComponent() != null
- && TextUtils.equals(info.getTargetComponent().getPackageName(),
- packageName);
- final ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
- if (info instanceof FolderInfo) {
- FolderInfo folderInfo = (FolderInfo) info;
- for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
- if (packageAndUserAndApp.evaluate(shortcutInfo, view)) {
- return true;
- }
- }
- }
- return false;
- };
-
- List<CellLayout> cellLayouts = new ArrayList<>(getPanelCount() + 1);
- cellLayouts.add(getHotseat());
- forEachVisiblePage(page -> cellLayouts.add((CellLayout) page));
-
- // Order: Preferred item, App icons in hotseat/workspace, app in folder in hotseat/workspace
- if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
- return getFirstMatch(cellLayouts, preferredItem, preferredItemInFolder,
- packageAndUserAndApp, packageAndUserAndAppInFolder);
- } else {
- // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
- // FolderAdaptiveIcon as the background.
- return getFirstMatch(cellLayouts, preferredItem, packageAndUserAndApp);
- }
- }
-
public View getHomescreenIconByItemId(final int id) {
return getFirstMatch((info, v) -> info != null && info.id == id);
}
@@ -3227,23 +3169,6 @@
return value[0];
}
- /**
- * Finds the first view matching the ordered operators across the given cell layouts by order.
- * @param cellLayouts List of CellLayouts to scan, in order of preference.
- * @param operators List of operators, in order starting from best matching operator.
- */
- View getFirstMatch(Iterable<CellLayout> cellLayouts, final ItemOperator... operators) {
- for (ItemOperator operator : operators) {
- for (CellLayout cellLayout : cellLayouts) {
- View match = mapOverCellLayout(cellLayout, operator);
- if (match != null) {
- return match;
- }
- }
- }
- return null;
- }
-
void clearDropTargets() {
mapOverItems(new ItemOperator() {
@Override
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index c255225..0c39067 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -357,7 +357,7 @@
public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
// Add "left" side of phone which is actually the top
return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ R.drawable.ic_split_left, R.string.split_screen_position_left,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
}
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index e69944a..cb1ba7d 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -370,28 +370,27 @@
public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
List<SplitPositionOption> options = new ArrayList<>(1);
// Add both left and right options if we're in tablet mode
- // TODO: Add in correct icons
if (dp.isTablet && dp.isLandscape) {
options.add(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ R.drawable.ic_split_right, R.string.split_screen_position_right,
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
options.add(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ R.drawable.ic_split_left, R.string.split_screen_position_left,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
} else {
if (dp.isSeascape()) {
// Add left/right options
options.add(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ R.drawable.ic_split_right, R.string.split_screen_position_right,
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
} else if (dp.isLandscape) {
options.add(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ R.drawable.ic_split_left, R.string.split_screen_position_left,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
} else {
// Only add top option
options.add(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_top,
+ R.drawable.ic_split_top, R.string.split_screen_position_top,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
}
}
@@ -542,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/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 539e3f8..ce2e136 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -115,7 +115,7 @@
public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
// Add "right" option which is actually the top
return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ R.drawable.ic_split_right, R.string.split_screen_position_right,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
}
diff --git a/src/com/android/launcher3/util/ActivityLifecycleCallbacksAdapter.java b/src/com/android/launcher3/util/ActivityLifecycleCallbacksAdapter.java
new file mode 100644
index 0000000..baa8418
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityLifecycleCallbacksAdapter.java
@@ -0,0 +1,29 @@
+package com.android.launcher3.util;
+
+import android.app.Activity;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+
+public interface ActivityLifecycleCallbacksAdapter extends ActivityLifecycleCallbacks {
+
+ default void onActivityCreated(Activity activity, Bundle bundle) {
+ }
+
+ default void onActivityDestroyed(Activity activity) {
+ }
+
+ default void onActivityPaused(Activity activity) {
+ }
+
+ default void onActivityResumed(Activity activity) {
+ }
+
+ default void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
+
+ default void onActivityStarted(Activity activity) {
+ }
+
+ default void onActivityStopped(Activity activity) {
+ }
+}
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index ab3083d..7917410 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -20,6 +20,7 @@
import android.os.UserHandle;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -85,8 +86,16 @@
}
static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
- return (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
- keys.contains(ShortcutKey.fromItemInfo(info));
+ return (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && keys.contains(ShortcutKey.fromItemInfo(info));
+ }
+
+ /**
+ * Returns a matcher for items within folders.
+ */
+ static ItemInfoMatcher forFolderMatch(ItemInfoMatcher childOperator) {
+ return (info, cn) -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+ .anyMatch(childOperator::matchesInfo);
}
/**
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index e2e3be7..f32f904 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -158,7 +158,7 @@
if (mContract == null) {
return;
}
- View icon = mLauncher.getWorkspace().getFirstMatchForAppClose(-1,
+ View icon = mLauncher.getFirstMatchForAppClose(-1,
mContract.componentName.getPackageName(), mContract.user);
boolean iconChanged = mIcon != icon;
@@ -182,7 +182,7 @@
lp.topMargin = Math.round(mIconPosition.top);
}
}
- if (iconChanged && !mIconBounds.isEmpty()) {
+ if (mIcon != null && iconChanged && !mIconBounds.isEmpty()) {
// Record the icon display
setCurrentIconVisible(true);
Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height());
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);
}