Merge "Revert "Align OverviewActionsView for 3 button taskbar"" into sc-v2-dev
diff --git a/quickstep/res/layout/rotate_suggestion.xml b/quickstep/res/layout/rotate_suggestion.xml
new file mode 100644
index 0000000..07cf0c8
--- /dev/null
+++ b/quickstep/res/layout/rotate_suggestion.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <com.android.systemui.shared.rotation.FloatingRotationButtonView
+ android:id="@+id/rotate_suggestion"
+ android:layout_width="@dimen/floating_rotation_button_diameter"
+ android:layout_height="@dimen/floating_rotation_button_diameter"
+ android:paddingStart="@dimen/navigation_key_padding"
+ android:paddingEnd="@dimen/navigation_key_padding"
+ android:layout_gravity="bottom|left"
+ android:scaleType="center"
+ android:visibility="invisible" />
+</FrameLayout>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..5d9e059
--- /dev/null
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+*/
+-->
+<resources>
+ <dimen name="navigation_key_padding">25dp</dimen>
+</resources>
diff --git a/quickstep/res/values-sw900dp/dimens.xml b/quickstep/res/values-sw900dp/dimens.xml
new file mode 100644
index 0000000..3efa5e3
--- /dev/null
+++ b/quickstep/res/values-sw900dp/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+*/
+-->
+<resources>
+ <!-- The maximum width of the navigation bar ripples. -->
+ <dimen name="key_button_ripple_max_width">76dp</dimen>
+
+ <!-- The padding around the navigation buttons -->
+ <dimen name="navigation_key_padding">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4ebf5cf..98d43f1 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -197,6 +197,19 @@
<!-- Minimum distance to swipe to trigger accessibility gesture -->
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
+ <!-- The maximum width of the navigation bar ripples. -->
+ <dimen name="key_button_ripple_max_width">95dp</dimen>
+
+ <dimen name="rounded_corner_content_padding">0dp</dimen>
+
+ <dimen name="navigation_key_padding">0dp</dimen>
+
+ <!-- Floating rotation button -->
+ <dimen name="floating_rotation_button_diameter">40dp</dimen>
+ <dimen name="floating_rotation_button_min_margin">20dp</dimen>
+ <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
+ <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
+
<!-- Taskbar -->
<dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
<dimen name="taskbar_icon_touch_size">48dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 11349bb..4b6dacd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -210,7 +210,15 @@
mControllers.rotationButtonController.setRotationButton(rotationButton, null);
} else {
mFloatingRotationButton = new FloatingRotationButton(mContext,
- R.string.accessibility_rotate_button);
+ R.string.accessibility_rotate_button,
+ R.layout.rotate_suggestion,
+ R.id.rotate_suggestion,
+ R.dimen.floating_rotation_button_min_margin,
+ R.dimen.rounded_corner_content_padding,
+ R.dimen.floating_rotation_button_taskbar_left_margin,
+ R.dimen.floating_rotation_button_taskbar_bottom_margin,
+ R.dimen.floating_rotation_button_diameter,
+ R.dimen.key_button_ripple_max_width);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 825abed..5f2b49d 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -17,7 +17,6 @@
package com.android.quickstep;
import android.content.Context;
-import android.util.Log;
import androidx.annotation.Nullable;
@@ -33,8 +32,6 @@
* {@link TaskViewSimulator}
*/
public class RemoteTargetGluer {
- private static final String TAG = "RemoteTargetGluer";
-
private RemoteTargetHandle[] mRemoteTargetHandles;
private SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
@@ -94,25 +91,18 @@
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
.getRunningSplitTaskIds();
- Log.d(TAG, "splitIds length: " + splitIds.length
- + " targetAppsLength: " + targets.apps.length
- + " remoteHandlesLength: " + mRemoteTargetHandles.length);
- if (splitIds.length == 0 && mRemoteTargetHandles.length > 1) {
- // There's a chance that between the creation of this class and assigning targets,
- // LauncherSplitScreenListener may have received callback that removes split
- mRemoteTargetHandles = new RemoteTargetHandle[]{mRemoteTargetHandles[0]};
- Log.w(TAG, "splitTaskIds changed between creation and assignment");
- }
RemoteAnimationTargetCompat primaryTaskTarget;
RemoteAnimationTargetCompat secondaryTaskTarget;
if (mRemoteTargetHandles.length == 1) {
// If we're not in split screen, the splitIds count doesn't really matter since we
- // should always hit this case. Right now there's no use case for multiple app targets
- // without being in split screen
- primaryTaskTarget = targets.apps[0];
+ // should always hit this case.
mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
- mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ if (targets.apps.length > 0) {
+ // Unclear why/when target.apps length == 0, but it sure does happen :(
+ primaryTaskTarget = targets.apps[0];
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ }
} else {
// split screen
primaryTaskTarget = targets.findTask(splitIds[0]);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index d9319a9..b6f9d58 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -37,6 +37,7 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -444,6 +445,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
+ } else if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SCREENSHOT, "sysuiproxy, no proxy available");
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index de7dbd6..b0c68c5 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -49,6 +49,7 @@
import com.android.internal.app.ChooserActivity;
import com.android.launcher3.BuildConfig;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.utilities.BitmapUtil;
@@ -77,6 +78,9 @@
public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
Rect screenshotBounds,
Insets visibleInsets, Task.TaskKey task) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SCREENSHOT, "image action utils calling into sysuiproxy");
+ }
systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot),
screenshotBounds, visibleInsets, task);
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 97052b2..dd58123 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -41,11 +41,10 @@
}
/**
- *
* @param target The view the accessibility event is initialized on.
* If null, this method has no effect.
- * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
- * @param text Optional text to add to the event, which will be announced to the user.
+ * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
+ * @param text Optional text to add to the event, which will be announced to the user.
*/
public static void sendCustomAccessibilityEvent(@Nullable View target, int type,
@Nullable String text) {
@@ -97,6 +96,16 @@
null);
}
+ /**
+ * Notify running tests of a folder opened.
+ */
+ public static void sendFolderOpenedEventToTest(Context context) {
+ final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+ if (accessibilityManager == null) return;
+
+ sendEventToTest(accessibilityManager, context, TestProtocol.FOLDER_OPENED_MESSAGE, null);
+ }
+
private static void sendEventToTest(
AccessibilityManager accessibilityManager,
Context context, String eventTag, Bundle data) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 879739f..daef682 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -78,6 +78,7 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
@@ -687,6 +688,7 @@
public void onAnimationEnd(Animator animation) {
mState = STATE_OPEN;
announceAccessibilityChanges();
+ AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext());
mContent.setFocusOnFirstChild();
}
@@ -1265,7 +1267,8 @@
PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
? (PendingAddShortcutInfo) d.dragInfo : null;
- WorkspaceItemInfo pasiSi = pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null;
+ WorkspaceItemInfo pasiSi =
+ pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null;
if (pasi != null && pasiSi == null) {
// There is no WorkspaceItemInfo, so we have to go through a configuration activity.
pasi.container = mInfo.id;
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 97398de..b72f462 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -373,7 +373,7 @@
protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
- itemBuilder.setIsWork(user != Process.myUserHandle());
+ itemBuilder.setIsWork(!Process.myUserHandle().equals(user));
return itemBuilder;
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 5a9c074..17d925c 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -21,6 +21,7 @@
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Insets;
import android.os.Build;
import android.os.Bundle;
@@ -148,6 +149,14 @@
TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest);
return response;
+ case TestProtocol.REQUEST_START_DRAG_THRESHOLD: {
+ final Resources resources = mContext.getResources();
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ resources.getDimensionPixelSize(R.dimen.deep_shortcuts_start_drag_threshold)
+ + resources.getDimensionPixelSize(R.dimen.pre_drag_view_scale));
+ return response;
+ }
+
default:
return null;
}
@@ -193,6 +202,7 @@
/**
* Generic interface for setting a fiend in bundle
+ *
* @param <T> the type of value being set
*/
public interface BundleSetter<T> {
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 5bf0342..db28902 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -25,6 +25,7 @@
public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS";
+ public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED";
public static final int NORMAL_STATE_ORDINAL = 0;
public static final int SPRING_LOADED_STATE_ORDINAL = 1;
public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -99,6 +100,7 @@
public static final String REQUEST_CLEAR_DATA = "clear-data";
public static final String REQUEST_IS_TABLET = "is-tablet";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
+ public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
"get-activities-created-count";
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
@@ -122,4 +124,5 @@
public static final String TASK_VIEW_ID_CRASH = "b/195430732";
public static final String NO_DROP_TARGET = "b/195031154";
public static final String NULL_INT_SET = "b/200572078";
+ public static final String NO_SCREENSHOT = "b/202414125";
}
diff --git a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
index 9c32e42..57f8bc7 100644
--- a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.widget;
+import android.annotation.SuppressLint;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Canvas;
@@ -30,6 +31,9 @@
import com.android.launcher3.R;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
/**
* A widget host views created while the host has not bind to the system service.
*/
@@ -75,8 +79,22 @@
&& mSetupTextLayout.getWidth() == availableWidth) {
return;
}
- mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
- Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+ try {
+ mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
+ Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+ } catch (IllegalArgumentException e) {
+ @SuppressLint("DrawAllocation") StringWriter stringWriter = new StringWriter();
+ @SuppressLint("DrawAllocation") PrintWriter printWriter = new PrintWriter(stringWriter);
+ mActivity.getDeviceProfile().dump(/*prefix=*/"", printWriter);
+ printWriter.flush();
+ String message = "b/203530620 "
+ + "- availableWidth: " + availableWidth
+ + ", getMeasuredWidth: " + getMeasuredWidth()
+ + ", getPaddingLeft: " + getPaddingLeft()
+ + ", getPaddingRight: " + getPaddingRight()
+ + ", deviceProfile: " + stringWriter.toString();
+ throw new IllegalArgumentException(message, e);
+ }
}
@Override
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 881f50c..2fa84b2 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -34,6 +34,8 @@
import com.android.launcher3.tapl.AppIcon;
import com.android.launcher3.tapl.AppIconMenu;
import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.tapl.Folder;
+import com.android.launcher3.tapl.FolderIcon;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.views.OptionsPopupView;
@@ -369,6 +371,48 @@
}
}
+ private AppIcon createShortcutIfNotExist(String name) {
+ AppIcon appIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
+ if (appIcon == null) {
+ AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ appIcon = allApps.getAppIcon(name);
+ appIcon.dragToWorkspace(false, false);
+ } finally {
+ allApps.unfreeze();
+ }
+ appIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
+ }
+ return appIcon;
+ }
+
+ @Test
+ @PortraitLandscape
+ public void testDragToFolder() throws Exception {
+ final AppIcon playStoreIcon = createShortcutIfNotExist("Play Store");
+ final AppIcon gmailIcon = createShortcutIfNotExist("Gmail");
+
+ FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
+
+ Folder folder = folderIcon.open();
+ folder.getAppIcon("Play Store");
+ folder.getAppIcon("Gmail");
+ Workspace workspace = folder.close();
+
+ assertNull("Gmail should be moved to a folder.",
+ workspace.tryGetWorkspaceAppIcon("Gmail"));
+ assertNull("Play Store should be moved to a folder.",
+ workspace.tryGetWorkspaceAppIcon("Play Store"));
+
+ final AppIcon youTubeIcon = createShortcutIfNotExist("YouTube");
+
+ folderIcon = youTubeIcon.dragToIcon(folderIcon);
+ folder = folderIcon.open();
+ folder.getAppIcon("YouTube");
+ folder.close();
+ }
+
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 41cdf0f..27a2375 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -32,6 +32,7 @@
import com.android.launcher3.allapps.WorkEduCard;
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import org.junit.After;
import org.junit.Before;
@@ -86,6 +87,7 @@
}
@Test
+ @ScreenRecord // b/202735477
public void workTabExists() {
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 21099b4..6da59da 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,8 +16,11 @@
package com.android.launcher3.tapl;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
@@ -29,7 +32,7 @@
/**
* App icon, whether in all apps or in workspace/
*/
-public final class AppIcon extends Launchable {
+public final class AppIcon extends Launchable implements FolderDragTarget {
private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
@@ -61,6 +64,29 @@
}
}
+ /**
+ * Drag the AppIcon to the given position of other icon. The drag must result in a folder.
+ *
+ * @param target the destination icon.
+ */
+ @NonNull
+ public FolderIcon dragToIcon(FolderDragTarget target) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) {
+ final Rect dropBounds = target.getDropLocationBounds();
+ Workspace.dragIconToWorkspace(
+ mLauncher, this,
+ () -> {
+ final Rect bounds = target.getDropLocationBounds();
+ return new Point(bounds.centerX(), bounds.centerY());
+ },
+ getLongPressIndicator());
+ FolderIcon result = target.getTargetFolder(dropBounds);
+ mLauncher.assertTrue("Can't find the target folder.", result != null);
+ return result;
+ }
+ }
+
@Override
protected void addExpectedEventsForLongClick() {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
@@ -80,4 +106,20 @@
protected String launchableType() {
return "app icon";
}
+
+ @Override
+ public Rect getDropLocationBounds() {
+ return mLauncher.getVisibleBounds(mObject);
+ }
+
+ @Override
+ public FolderIcon getTargetFolder(Rect bounds) {
+ for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) {
+ final Rect folderIconBounds = folderIcon.getDropLocationBounds();
+ if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) {
+ return folderIcon;
+ }
+ }
+ return null;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
new file mode 100644
index 0000000..dba308d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+public class Folder {
+
+ protected static final String FOLDER_CONTENT_RES_ID = "folder_content";
+
+ private final UiObject2 mContainer;
+ private final LauncherInstrumentation mLauncher;
+
+ Folder(LauncherInstrumentation launcher) {
+ this.mLauncher = launcher;
+ this.mContainer = launcher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
+ }
+
+ /**
+ * Find an app icon with given name or raise assertion error.
+ */
+ @NonNull
+ public AppIcon getAppIcon(String appName) {
+ try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer(
+ "Want to get app icon in folder")) {
+ return new AppIcon(mLauncher,
+ mLauncher.waitForObjectInContainer(
+ mContainer,
+ AppIcon.getAppIconSelector(appName, mLauncher)));
+ }
+ }
+
+ private void touchOutsideFolder() {
+ Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer);
+ final long downTime = SystemClock.uptimeMillis();
+ Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+ containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
+ containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
+ }
+
+ /**
+ * CLose opened folder if possible. It throws assertion error if the folder is already closed.
+ */
+ public Workspace close() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "Want to close opened folder")) {
+ mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
+ touchOutsideFolder();
+ mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID);
+ return mLauncher.getWorkspace();
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
new file mode 100644
index 0000000..d797418
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
@@ -0,0 +1,25 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Rect;
+
+public interface FolderDragTarget {
+ Rect getDropLocationBounds();
+
+ FolderIcon getTargetFolder(Rect bounds);
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
new file mode 100644
index 0000000..2e79d70
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
@@ -0,0 +1,64 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.TestProtocol;
+
+/**
+ * Folder Icon, an app folder in workspace.
+ */
+public class FolderIcon implements FolderDragTarget {
+
+ protected final UiObject2 mObject;
+ protected final LauncherInstrumentation mLauncher;
+
+ FolderIcon(LauncherInstrumentation launcher, UiObject2 icon) {
+ mObject = icon;
+ mLauncher = launcher;
+ }
+
+ /**
+ * Open and return a folder or raise assertion error.
+ */
+ @NonNull
+ public Folder open() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer("open folder")) {
+ mLauncher.executeAndWaitForLauncherEvent(() -> mLauncher.clickLauncherObject(mObject),
+ event -> TestProtocol.FOLDER_OPENED_MESSAGE.equals(
+ event.getClassName().toString()),
+ () -> "Fail to open folder.",
+ "open folder");
+ }
+ return new Folder(mLauncher);
+ }
+
+ @Override
+ public Rect getDropLocationBounds() {
+ return mLauncher.getVisibleBounds(mObject.getParent());
+ }
+
+ @Override
+ public FolderIcon getTargetFolder(Rect bounds) {
+ return this;
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7ffdf4c..3ac5fa5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -80,6 +81,7 @@
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
@@ -768,6 +770,47 @@
}
/**
+ * Get the resource ID of visible floating view.
+ */
+ private Optional<String> getFloatingResId() {
+ if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
+ return Optional.of(CONTEXT_MENU_RES_ID);
+ }
+ if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) {
+ return Optional.of(FOLDER_CONTENT_RES_ID);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content.
+ */
+ private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) {
+ final Point displaySize = getRealDisplaySize();
+
+ final Optional<String> floatingRes = getFloatingResId();
+
+ if (!floatingRes.isPresent()) {
+ return;
+ }
+
+ GestureScope gestureScope = gestureStartFromLauncher
+ ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
+ : GestureScope.OUTSIDE_WITH_PILFER;
+ linearGesture(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, gestureScope);
+
+ try (LauncherInstrumentation.Closable c1 = addContextLayer(
+ String.format("Swiped up from floating view %s to home", floatingRes.get()))) {
+ waitUntilLauncherObjectGone(floatingRes.get());
+ waitForLauncherObject(getAnyObjectSelector());
+ }
+ }
+
+ /**
* Presses nav bar home button.
*
* @return the Workspace object.
@@ -791,21 +834,9 @@
? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
: isLauncherVisible();
- if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
- GestureScope gestureScope = gestureStartFromLauncher
- ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
- : GestureScope.OUTSIDE_WITH_PILFER;
- linearGesture(
- displaySize.x / 2, displaySize.y - 1,
- displaySize.x / 2, 0,
- ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
- false, gestureScope);
- try (LauncherInstrumentation.Closable c1 = addContextLayer(
- "Swiped up from context menu to home")) {
- waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
- waitForLauncherObject(getAnyObjectSelector());
- }
- }
+ // CLose floating views before going back to home.
+ swipeUpToCloseFloatingView(gestureStartFromLauncher);
+
if (hasLauncherObject(WORKSPACE_RES_ID)) {
log(action = "already at home");
} else {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 288c853..0145690 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -36,7 +36,10 @@
import com.android.launcher3.testing.TestProtocol;
+import java.util.List;
+import java.util.function.Supplier;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* Operations on the workspace screen.
@@ -170,40 +173,100 @@
mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
}
- static void dragIconToWorkspace(
- LauncherInstrumentation launcher, Launchable launchable, Point dest,
- String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
- Runnable expectLongClickEvents) {
- LauncherInstrumentation.log("dragIconToWorkspace: begin");
- final Point launchableCenter = launchable.getObject().getVisibleCenter();
- final long downTime = SystemClock.uptimeMillis();
- launcher.runToState(
- () -> {
- launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
- launchableCenter, LauncherInstrumentation.GestureScope.INSIDE);
- LauncherInstrumentation.log("dragIconToWorkspace: sent down");
- expectLongClickEvents.run();
- launcher.waitForLauncherObject(longPressIndicator);
- LauncherInstrumentation.log("dragIconToWorkspace: indicator");
- launcher.movePointer(launchableCenter, dest, 10, downTime, true,
- LauncherInstrumentation.GestureScope.INSIDE);
- },
- SPRING_LOADED_STATE_ORDINAL,
- "long-pressing and moving");
- LauncherInstrumentation.log("dragIconToWorkspace: moved pointer");
+ private static int getStartDragThreshold(LauncherInstrumentation launcher) {
+ return launcher.getTestInfo(TestProtocol.REQUEST_START_DRAG_THRESHOLD).getInt(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
+ /**
+ * Finds folder icons in the current workspace.
+ *
+ * @return a list of folder icons.
+ */
+ List<FolderIcon> getFolderIcons() {
+ final UiObject2 workspace = verifyActiveContainer();
+ return mLauncher.getObjectsInContainer(workspace, "folder_icon_name").stream().map(
+ o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList());
+ }
+
+ /**
+ * Drag an icon up with a short distance that makes workspace go to spring loaded state.
+ *
+ * @return the position after dragging.
+ */
+ private static Point dragIconToSpringLoaded(LauncherInstrumentation launcher, long downTime,
+ UiObject2 icon,
+ String longPressIndicator, Runnable expectLongClickEvents) {
+ final Point iconCenter = icon.getVisibleCenter();
+ final Point dragStartCenter = new Point(iconCenter.x,
+ iconCenter.y - getStartDragThreshold(launcher));
+
+ launcher.runToState(() -> {
+ launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+ iconCenter, LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.log("dragIconToSpringLoaded: sent down");
+ expectLongClickEvents.run();
+ launcher.waitForLauncherObject(longPressIndicator);
+ LauncherInstrumentation.log("dragIconToSpringLoaded: indicator");
+ launcher.movePointer(iconCenter, dragStartCenter, 10, downTime, true,
+ LauncherInstrumentation.GestureScope.INSIDE);
+ }, SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
+ return dragStartCenter;
+ }
+
+ private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime,
+ @Nullable Runnable expectedEvents) {
launcher.runToState(
() -> launcher.sendPointer(
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
LauncherInstrumentation.GestureScope.INSIDE),
NORMAL_STATE_ORDINAL,
"sending UP event");
- if (startsActivity || isWidgetShortcut) {
- launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
+ if (expectedEvents != null) {
+ expectedEvents.run();
}
- LauncherInstrumentation.log("dragIconToWorkspace: end");
+ LauncherInstrumentation.log("dropIcon: end");
launcher.waitUntilLauncherObjectGone("drop_target_bar");
}
+ static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
+ Point dest, String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
+ Runnable expectLongClickEvents) {
+ Runnable expectDropEvents = null;
+ if (startsActivity || isWidgetShortcut) {
+ expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_START);
+ }
+ dragIconToWorkspace(launcher, launchable, () -> dest, longPressIndicator,
+ expectLongClickEvents, expectDropEvents);
+ }
+
+ /**
+ * Drag icon in workspace to else where.
+ * This function expects the launchable is inside the workspace and there is no drop event.
+ */
+ static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
+ Supplier<Point> destSupplier, String longPressIndicator) {
+ dragIconToWorkspace(launcher, launchable, destSupplier, longPressIndicator,
+ () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), null);
+ }
+
+ static void dragIconToWorkspace(
+ LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest,
+ String longPressIndicator, Runnable expectLongClickEvents,
+ @Nullable Runnable expectDropEvents) {
+ try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
+ "want to drag icon to workspace")) {
+ final long downTime = SystemClock.uptimeMillis();
+ final Point dragStartCenter = dragIconToSpringLoaded(launcher, downTime,
+ launchable.getObject(), longPressIndicator, expectLongClickEvents);
+ final Point targetDest = dest.get();
+ launcher.movePointer(dragStartCenter, targetDest, 10, downTime, true,
+ LauncherInstrumentation.GestureScope.INSIDE);
+ dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
+ }
+ }
+
/**
* Flings to get to screens on the right. Waits for scrolling and a possible overscroll
* recoil to complete.