Snap for 7880955 from d9db871f79 to sc-v2-release

Change-Id: Ia3ff4dd892a6d05c3debdd95966754bdbeb54b6a
This commit is contained in:
Android Build Coastguard Worker
2021-11-03 23:08:26 +00:00
20 changed files with 553 additions and 93 deletions
@@ -234,14 +234,6 @@ public class NavbarButtonsViewController {
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
}
public void onDestroy() {
mPropertyHolders.clear();
mControllers.rotationButtonController.unregisterListeners();
if (mFloatingRotationButton != null) {
mFloatingRotationButton.hide();
}
}
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
TaskbarNavButtonController navButtonController) {
@@ -438,6 +430,14 @@ public class NavbarButtonsViewController {
return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
}
public void onDestroy() {
mPropertyHolders.clear();
mControllers.rotationButtonController.unregisterListeners();
if (mFloatingRotationButton != null) {
mFloatingRotationButton.hide();
}
}
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {
@@ -37,6 +37,7 @@ import android.view.RemoteAnimationAdapter;
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 @@ public class SystemUiProxy implements ISystemUiProxy,
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
} else if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SCREENSHOT, "sysuiproxy, no proxy available");
}
}
@@ -49,6 +49,7 @@ import androidx.core.content.FileProvider;
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 class ImageActionUtils {
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);
}
@@ -16,8 +16,6 @@
package com.android.quickstep.views;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -32,7 +30,6 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.SysUINavigationMode;
@@ -149,7 +146,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
public void setInsets(Rect insets) {
mInsets.set(insets);
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
updatePaddingAndTranslations();
updateHorizontalPadding();
}
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -192,37 +189,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
}
/**
* Aligns OverviewActionsView vertically with and offsets horizontal position based on
* 3 button nav container in taskbar.
*/
private void updatePaddingAndTranslations() {
boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
SysUINavigationMode.getMode(getContext()) == THREE_BUTTONS;
if (alignFor3ButtonTaskbar) {
// Add extra horizontal spacing
int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
if (isLayoutRtl()) {
setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0);
} else {
setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0);
}
// Align vertically, using taskbar height + mDp.taskbarOffsetY() to guestimate
// where the button nav top is
View startActionView = findViewById(R.id.action_buttons);
int marginBottom = getOverviewActionsBottomMarginPx(
SysUINavigationMode.getMode(getContext()), mDp);
int actionsTop = (mDp.heightPx - marginBottom - mInsets.bottom);
int navTop = mDp.heightPx - (mDp.taskbarSize + mDp.getTaskbarOffsetY());
int transY = navTop - actionsTop
+ ((mDp.taskbarSize - startActionView.getHeight()) / 2);
setTranslationY(transY);
} else {
setPadding(mInsets.left, 0, mInsets.right, 0);
setTranslationX(0);
setTranslationY(0);
}
private void updateHorizontalPadding() {
setPadding(mInsets.left, 0, mInsets.right, 0);
}
/** Updates vertical margins for different navigation mode or configuration changes. */
@@ -1094,7 +1094,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
for (int i = 0; i < taskCount; i++) {
View v = getTaskViewAt(i);
if (!(v instanceof GroupedTaskView)) {
continue;
return;
}
GroupedTaskView gtv = (GroupedTaskView) v;
gtv.onTaskListVisibilityChanged(false);
@@ -31,6 +31,7 @@ import androidx.test.uiautomator.Until;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.tapl.Background;
import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
import com.android.launcher3.tapl.Overview;
@@ -49,6 +50,9 @@ import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplTestsQuickstep extends AbstractQuickStepTest {
private static final String APP_NAME = "LauncherTestApp";
@Before
public void setUp() throws Exception {
super.setUp();
@@ -286,6 +290,30 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
getAndAssertBackground();
}
// TODO(b/204830798): test with all navigation modes(add @NavigationModeSwitch annotation)
// after the bug resolved.
@Test
@PortraitLandscape
@ScreenRecord
public void testPressBack() throws Exception {
mLauncher.getWorkspace().switchToAllApps();
mLauncher.pressBack();
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
try {
allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
} finally {
allApps.unfreeze();
}
mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
mLauncher.pressBack();
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
}
@Test
@PortraitLandscape
public void testOverviewForTablet() throws Exception {
@@ -41,11 +41,10 @@ public class AccessibilityManagerCompat {
}
/**
*
* @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 @@ public class AccessibilityManagerCompat {
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) {
+4 -1
View File
@@ -78,6 +78,7 @@ import com.android.launcher3.Utilities;
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 class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public void onAnimationEnd(Animator animation) {
mState = STATE_OPEN;
announceAccessibilityChanges();
AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext());
mContent.setFocusOnFirstChild();
}
@@ -1265,7 +1267,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
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;
@@ -373,7 +373,7 @@ public class ItemInfo {
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;
}
@@ -21,6 +21,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
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 @@ public class TestInformationHandler implements ResourceBasedOverride {
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 @@ public class TestInformationHandler implements ResourceBasedOverride {
/**
* Generic interface for setting a fiend in bundle
*
* @param <T> the type of value being set
*/
public interface BundleSetter<T> {
@@ -25,6 +25,7 @@ public final class TestProtocol {
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 final class TestProtocol {
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 final class TestProtocol {
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";
}
@@ -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 android.widget.RemoteViews;
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 @@ public class DeferredAppWidgetHostView extends LauncherAppWidgetHostView {
&& 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
@@ -211,7 +211,8 @@ public abstract class AbstractLauncherUiTest {
}
protected TestRule getRulesInsideActivityMonitor() {
final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this))
final RuleChain inner = RuleChain
.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mDevice, mLauncher));
return TestHelpers.isInLauncherProcess()
@@ -34,6 +34,8 @@ import com.android.launcher3.tapl.AllApps;
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,69 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
}
}
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();
}
@Test
@PortraitLandscape
public void testPressBack() throws Exception {
mLauncher.getWorkspace().switchToAllApps();
mLauncher.pressBack();
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
try {
allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
} finally {
allApps.unfreeze();
}
mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
mLauncher.pressBack();
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
}
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
@@ -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 @@ import java.util.regex.Pattern;
/**
* 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 @@ public final class AppIcon extends Launchable {
}
}
/**
* 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 @@ public final class AppIcon extends Launchable {
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;
}
}
@@ -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();
}
}
}
@@ -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);
}
@@ -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;
}
}
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.DONT_KILL_APP;
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;
@@ -75,11 +76,13 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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;
@@ -108,6 +111,9 @@ public final class LauncherInstrumentation {
static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
static final Pattern EVENT_KEY_BACK_DOWN = getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
static final Pattern EVENT_KEY_BACK_UP = getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
private final String mLauncherPackage;
private Boolean mIsLauncher3;
private long mTestStartTime = -1;
@@ -123,7 +129,8 @@ public final class LauncherInstrumentation {
// Where the gesture happens: outside of Launcher, inside or from inside to outside and
// whether the gesture recognition triggers pilfer.
public enum GestureScope {
OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE
OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE,
INSIDE_TO_OUTSIDE_WITHOUT_PILFER,
}
// Base class for launcher containers.
@@ -193,6 +200,10 @@ public final class LauncherInstrumentation {
return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
}
private static Pattern getKeyEventPattern(String action, String keyCode) {
return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
}
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
*/
@@ -767,6 +778,47 @@ public final class LauncherInstrumentation {
}
}
/**
* 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.
*
@@ -791,21 +843,9 @@ public final class LauncherInstrumentation {
? !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 {
@@ -848,6 +888,38 @@ public final class LauncherInstrumentation {
}
}
/**
* Press navbar back button or swipe back if in gesture navigation mode.
*/
public void pressBack() {
try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) {
waitForLauncherInitialized();
final boolean launcherVisible =
isTablet() ? isLauncherContainerVisible() : isLauncherVisible();
if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
final Point displaySize = getRealDisplaySize();
final GestureScope gestureScope =
launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
: GestureScope.OUTSIDE_WITHOUT_PILFER;
linearGesture(0, displaySize.y / 2, displaySize.x / 2, displaySize.y / 2,
10, false, gestureScope);
} else {
waitForNavigationUiObject("back").click();
if (isTablet()) {
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
} else if (!isLauncher3() && getNavigationModel() == NavigationModel.TWO_BUTTON) {
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
}
}
if (launcherVisible) {
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
}
}
}
private static BySelector getAnyObjectSelector() {
return By.textStartsWith("");
}
@@ -857,6 +929,11 @@ public final class LauncherInstrumentation {
return hasLauncherObject(getAnyObjectSelector());
}
boolean isLauncherContainerVisible() {
final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID};
return Arrays.stream(containerResources).anyMatch(r -> hasLauncherObject(r));
}
/**
* Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
* launcher is not in that state.
@@ -1383,6 +1460,7 @@ public final class LauncherInstrumentation {
break;
case MotionEvent.ACTION_UP:
if (notLauncher3 && gestureScope != GestureScope.INSIDE
&& gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
&& (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
|| gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
@@ -36,7 +36,10 @@ import androidx.test.uiautomator.UiObject2;
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 @@ public final class Workspace extends Home {
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.