diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 770dfb2e07..de0b14d4fa 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -117,14 +117,15 @@ public class QuickstepModelDelegate extends ModelDelegate { // TODO: Implement caching and preloading super.loadItems(ums, pinnedShortcuts); - WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory( - mApp, ums, pinnedShortcuts, mIDP.numDatabaseAllAppsColumns); - FixedContainerItems allAppsItems = new FixedContainerItems(mAllAppsState.containerId, - mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get)); - mDataModel.extraItems.put(mAllAppsState.containerId, allAppsItems); + WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, + mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId); + FixedContainerItems allAppsPredictionItems = new FixedContainerItems( + mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(), + allAppsFactory, ums.allUsers::get)); + mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems); - WorkspaceItemFactory hotseatFactory = - new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numDatabaseHotseatIcons); + WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, + mIDP.numDatabaseHotseatIcons, mHotseatState.containerId); FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId, mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get)); mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems); @@ -432,15 +433,17 @@ public class QuickstepModelDelegate extends ModelDelegate { private final UserManagerState mUMS; private final Map mPinnedShortcuts; private final int mMaxCount; + private final int mContainer; private int mReadCount = 0; protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums, - Map pinnedShortcuts, int maxCount) { + Map pinnedShortcuts, int maxCount, int container) { mAppState = appState; mUMS = ums; mPinnedShortcuts = pinnedShortcuts; mMaxCount = maxCount; + mContainer = container; } @Nullable @@ -458,6 +461,7 @@ public class QuickstepModelDelegate extends ModelDelegate { return null; } AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user)); + info.container = mContainer; mAppState.getIconCache().getTitleAndIcon(info, lai, false); mReadCount++; return info.makeWorkspaceItem(mAppState.getContext()); @@ -472,6 +476,7 @@ public class QuickstepModelDelegate extends ModelDelegate { return null; } WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext()); + wii.container = mContainer; mAppState.getIconCache().getShortcutIcon(wii, si); mReadCount++; return wii; diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 6e0b71dca0..520487e98a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; import android.animation.Animator; @@ -169,6 +170,13 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + if (ENABLE_SHELL_TRANSITIONS + && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) { + // Launcher is resumed, but in a state where taskbar is still independent, so + // ignore the state change. + return null; + } + mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed); return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation); } diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java index c4837a0c51..0e62da3f63 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java @@ -16,8 +16,7 @@ package com.android.launcher3.taskbar.allapps; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; -import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; import android.animation.PropertyValuesHolder; import android.content.Context; @@ -60,7 +59,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView // TODO (b/238651489): Refactor state management to avoid need for double check FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView(); if (floatingTask != null) { + // We are in split selection state currently, transitioning to another state DragLayer dragLayer = mLauncher.getDragLayer(); RectF onScreenRectF = new RectF(); Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask, @@ -127,8 +127,8 @@ public abstract class BaseRecentsViewStateController ); setter.setFloat( - mRecentsView, - FIRST_FLOATING_TASK_TRANSLATE_OFFSCREEN, + mRecentsView.getFirstFloatingTaskView(), + PRIMARY_TRANSLATE_OFFSCREEN, mRecentsView.getPagedOrientationHandler() .getFloatingTaskOffscreenTranslationTarget( floatingTask, @@ -140,14 +140,14 @@ public abstract class BaseRecentsViewStateController ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN, LINEAR )); - setter.setFloat( - mRecentsView, - SPLIT_INSTRUCTIONS_FADE, - 1, + setter.setViewAlpha( + mRecentsView.getSplitInstructionsView(), + 0, config.getInterpolator( ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, LINEAR - )); + ) + ); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 0f3ea15b80..c9bc260a21 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -64,6 +64,7 @@ import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; +import android.os.SystemProperties; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.View; @@ -159,6 +160,9 @@ import java.util.stream.Stream; public class QuickstepLauncher extends Launcher { + public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM = + SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false); + public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false; /** * Reusable command for applying the shelf height on the background thread. @@ -349,16 +353,18 @@ public class QuickstepLauncher extends Launcher { */ private void onStateOrResumeChanging(boolean inTransition) { LauncherState state = getStateManager().getState(); - boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0; - if (started) { - DeviceProfile profile = getDeviceProfile(); - boolean willUserBeActive = - (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; - boolean visible = (state == NORMAL || state == OVERVIEW) - && (willUserBeActive || isUserActive()) - && !profile.isVerticalBarLayout(); - UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, - profile.hotseatBarSizePx); + if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { + boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0; + if (started) { + DeviceProfile profile = getDeviceProfile(); + boolean willUserBeActive = + (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; + boolean visible = (state == NORMAL || state == OVERVIEW) + && (willUserBeActive || isUserActive()) + && !profile.isVerticalBarLayout(); + UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, + profile.hotseatBarSizePx); + } } if (state == NORMAL && !inTransition) { ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index 9f2efc4962..e21f14fa6c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -16,14 +16,17 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; import android.content.Context; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.util.Themes; /** @@ -41,9 +44,9 @@ public class AllAppsState extends LauncherState { @Override public int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) { - return !context.getDeviceProfile().isTablet && isToState - ? 600 - : isToState ? 500 : 300; + return context.getDeviceProfile().isTablet + ? 500 + : isToState ? 600 : 300; } @Override @@ -77,10 +80,23 @@ public class AllAppsState extends LauncherState { } @Override - protected float getDepthUnchecked(Context context) { - // The scrim fades in at approximately 50% of the swipe gesture. - // This means that the depth should be greater than 1, in order to fully zoom out. - return 2f; + protected + float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) { + if (context.getDeviceProfile().isTablet) { + // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps. + // When depth is 0, wallpaper zoom is set to maxWallpaperScale. + // When depth is 1, wallpaper zoom is set to 1. + // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale: + float maxWallpaperScale = context.getResources().getFloat( + com.android.internal.R.dimen.config_wallpaperMaxScale); + return Utilities.mapToRange( + maxWallpaperScale * context.getDeviceProfile().workspaceContentScale, + maxWallpaperScale, 1f, 0f, 1f, LINEAR); + } else { + // The scrim fades in at approximately 50% of the swipe gesture. + // This means that the depth should be greater than 1, in order to fully zoom out. + return 2f; + } } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index 2eade67cc1..4150d40bea 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import android.content.Context; import android.graphics.Color; @@ -101,6 +102,12 @@ public class BackgroundAppState extends OverviewState { return Color.TRANSPARENT; } + @Override + public boolean isTaskbarAlignedWithHotseat(Launcher launcher) { + if (ENABLE_SHELL_TRANSITIONS) return false; + return super.isTaskbarAlignedWithHotseat(launcher); + } + public static float[] getOverviewScaleAndOffsetForBackgroundState( BaseDraggingActivity activity) { return new float[] { diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 6e616f388b..1f7b7de0e1 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -33,6 +33,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; +import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; @@ -1461,12 +1462,13 @@ public abstract class AbsSwipeUpHandler, homeToWindowPositionMap.invert(windowToHomePositionMap); windowToHomePositionMap.mapRect(startRect); + final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat(); final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext) .startSwipePipToHome(taskInfo.topActivity, taskInfo.topActivityInfo, runningTaskTarget.taskInfo.pictureInPictureParams, homeRotation, - mDp.hotseatBarSizePx); + hotseatKeepClearArea); final Rect appBounds = new Rect(); final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration; // Adjust the appBounds for TaskBar by using the calculated window crop Rect @@ -1529,6 +1531,35 @@ public abstract class AbsSwipeUpHandler, return swipePipToHomeAnimator; } + private Rect getKeepClearAreaForHotseat() { + Rect keepClearArea; + if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { + // make the height equal to hotseatBarSizePx only + keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, 0); + return keepClearArea; + } + // the keep clear area in global screen coordinates, in pixels + if (mDp.isPhone) { + if (mDp.isSeascape()) { + // in seascape the Hotseat is on the left edge of the screen + keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, mDp.heightPx); + } else if (mDp.isLandscape) { + // in landscape the Hotseat is on the right edge of the screen + keepClearArea = new Rect(mDp.widthPx - mDp.hotseatBarSizePx, 0, + mDp.widthPx, mDp.heightPx); + } else { + // in portrait mode the Hotseat is at the bottom of the screen + keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, + mDp.widthPx, mDp.heightPx); + } + } else { + // large screens have Hotseat always at the bottom of the screen + keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, + mDp.widthPx, mDp.heightPx); + } + return keepClearArea; + } + private void startInterceptingTouchesForGesture() { if (mRecentsAnimationController == null) { return; diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 0ec7e628b5..8e9ff2faa8 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -28,7 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.graphics.Bitmap; +import android.content.pm.ShortcutInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; @@ -509,11 +509,12 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) { + PictureInPictureParams pictureInPictureParams, int launcherRotation, + Rect hotseatKeepClearArea) { if (mPip != null) { try { return mPip.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, shelfHeight); + pictureInPictureParams, launcherRotation, hotseatKeepClearArea); } catch (RemoteException e) { Log.w(TAG, "Failed call startSwipePipToHome", e); } @@ -602,7 +603,21 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter); } catch (RemoteException e) { - Log.w(TAG, "Failed call startTasksWithLegacyTransition"); + Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition"); + } + } + } + + public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId, + Bundle mainOptions, Bundle sideOptions, + @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio, + RemoteAnimationAdapter adapter) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId, + mainOptions, sideOptions, sidePosition, splitRatio, adapter); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition"); } } } diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index a809c9c09b..93170cb79b 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -195,7 +195,8 @@ public final class TaskViewUtils { int taskIndex = recentsView.indexOfChild(v); Context context = v.getContext(); - DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); + BaseActivity baseActivity = BaseActivity.fromContext(context); + DeviceProfile dp = baseActivity.getDeviceProfile(); boolean showAsGrid = dp.isTablet; boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage() && !showAsGrid; @@ -368,7 +369,7 @@ public final class TaskViewUtils { }); if (depthController != null) { - out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context), + out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE_INTERPOLATOR); } } diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index eb739a6972..7c96bf8a78 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -226,6 +226,11 @@ public class FallbackRecentsView extends RecentsView PRIMARY_TRANSLATE_OFFSCREEN = + new FloatProperty("floatingTaskPrimaryTranslateOffscreen") { + @Override + public void setValue(FloatingTaskView view, float translation) { + ((RecentsView) view.mActivity.getOverviewPanel()).getPagedOrientationHandler() + .setFloatingTaskPrimaryTranslation( + view, + translation, + view.mActivity.getDeviceProfile() + ); + } + + @Override + public Float get(FloatingTaskView view) { + return ((RecentsView) view.mActivity.getOverviewPanel()) + .getPagedOrientationHandler() + .getFloatingTaskPrimaryTranslation( + view, + view.mActivity.getDeviceProfile() + ); + } + }; + private FloatingTaskThumbnailView mThumbnailView; private SplitPlaceholderView mSplitPlaceholderView; private RectF mStartingPosition; diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index a870f9cc8f..2539ed6d26 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -319,4 +319,10 @@ public class GroupedTaskView extends TaskView { super.applyThumbnailSplashAlpha(); mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha); } + + @Override + void setThumbnailVisibility(int visibility) { + super.setThumbnailVisibility(visibility); + mSnapshotView2.setVisibility(visibility); + } } diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 6a33d36ab4..de7ccad7a3 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -119,6 +119,11 @@ public class LauncherRecentsView extends RecentsView FIRST_FLOATING_TASK_TRANSLATE_OFFSCREEN = - new FloatProperty("firstFloatingTaskTranslateOffscreen") { - @Override - public void setValue(RecentsView view, float translation) { - view.getPagedOrientationHandler().setFloatingTaskPrimaryTranslation( - view.mFirstFloatingTaskView, - translation, - view.mActivity.getDeviceProfile() - ); - } - - @Override - public Float get(RecentsView view) { - return view.getPagedOrientationHandler().getFloatingTaskPrimaryTranslation( - view.mFirstFloatingTaskView, - view.mActivity.getDeviceProfile() - ); - } - }; - - public static final FloatProperty SPLIT_INSTRUCTIONS_FADE = - new FloatProperty("splitInstructionsFade") { - @Override - public void setValue(RecentsView view, float fade) { - view.mSplitInstructionsView.setAlpha(1 - fade); - } - - @Override - public Float get(RecentsView view) { - return 1 - view.mSplitInstructionsView.getAlpha(); - } - }; - // OverScroll constants private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; @@ -2827,7 +2794,11 @@ public abstract class RecentsView ICON_ALPHA = + new FloatProperty("iconAlpha") { + @Override + public void setValue(TaskView taskView, float v) { + taskView.mIconView.setAlpha(v); + } + + @Override + public Float get(TaskView taskView) { + return taskView.mIconView.getAlpha(); + } + }; + @Nullable protected Task mTask; protected TaskThumbnailView mSnapshotView; @@ -1488,6 +1501,10 @@ public class TaskView extends FrameLayout implements Reusable { return display != null ? display.getDisplayId() : DEFAULT_DISPLAY; } + void setThumbnailVisibility(int visibility) { + mSnapshotView.setVisibility(visibility); + } + /** * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. */ diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml index 82b0b8d74e..95ebd94d0e 100644 --- a/res/layout/hotseat.xml +++ b/res/layout/hotseat.xml @@ -21,4 +21,5 @@ android:layout_height="match_parent" android:theme="@style/HomeScreenElementTheme" android:importantForAccessibility="no" + android:preferKeepClear="true" launcher:containerType="hotseat" /> \ No newline at end of file diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 448a56bcc1..86699499a5 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -106,13 +106,13 @@ "Funkcja wyłączona przez administratora" "Zezwalaj na obrót ekranu głównego" "Po obróceniu telefonu" - "Plakietki z powiadomieniami" + "Kropki powiadomień" "Włączono" "Wyłączono" "Wymagany jest dostęp do powiadomień" - "Aby pokazać plakietki z powiadomieniami, włącz powiadomienia aplikacji %1$s" + "Aby pokazywać kropki powiadomień, włącz powiadomienia aplikacji %1$s" "Zmień ustawienia" - "Pokaż plakietki z powiadomieniami" + "Pokaż kropki powiadomień" "Opcje programisty" "Dodawaj ikony aplikacji do ekranu głównego" "W przypadku nowych aplikacji" diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 4532ed4b05..c3b5392cf5 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -265,7 +265,8 @@ public abstract class LauncherState implements BaseState { * * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs. */ - public final float getDepth(Context context) { + public final + float getDepth(DEVICE_PROFILE_CONTEXT context) { return getDepth(context, BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode); } @@ -275,14 +276,16 @@ public abstract class LauncherState implements BaseState { * * @see #getDepth(Context). */ - public final float getDepth(Context context, boolean isMultiWindowMode) { + public final + float getDepth(DEVICE_PROFILE_CONTEXT context, boolean isMultiWindowMode) { if (isMultiWindowMode) { return 0; } return getDepthUnchecked(context); } - protected float getDepthUnchecked(Context context) { + protected + float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) { return 0f; } diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index 0a77aa7bcf..12b4223bac 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -57,6 +57,10 @@ public class Interpolators { public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f); public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f); + /** + * The default emphasized interpolator. Used for hero / emphasized movement of content. + */ + public static final Interpolator EMPHASIZED = createEmphasizedInterpolator(); public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator( 0.3f, 0f, 0.8f, 0.15f); public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator( @@ -87,7 +91,6 @@ public class Interpolators { public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL = v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v)); - /** * Inversion of ZOOM_OUT, compounded with an ease-out. */ @@ -218,4 +221,14 @@ public class Interpolators { public static Interpolator reverse(Interpolator interpolator) { return t -> 1 - interpolator.getInterpolation(1 - t); } + + // Create the default emphasized interpolator + private static PathInterpolator createEmphasizedInterpolator() { + Path path = new Path(); + // Doing the same as fast_out_extra_slow_in + path.moveTo(0f, 0f); + path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f); + path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f); + return new PathInterpolator(path); + } } diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 2aa9ddeb50..c44e1e1784 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -342,6 +342,11 @@ public class StateManager> { public void onAnimationSuccess(Animator animator) { onStateTransitionEnd(state); } + + @Override + public void onAnimationCancel(Animator animation) { + onStateTransitionFailed(state); + } }; } @@ -354,6 +359,12 @@ public class StateManager> { } } + private void onStateTransitionFailed(STATE_TYPE state) { + for (int i = mListeners.size() - 1; i >= 0; i--) { + mListeners.get(i).onStateTransitionFailed(state); + } + } + private void onStateTransitionEnd(STATE_TYPE state) { // Only change the stable states after the transitions have finished if (state != mCurrentStableState) { @@ -588,6 +599,11 @@ public class StateManager> { default void onStateTransitionStart(STATE_TYPE toState) { } + /** + * If the state transition animation fails (e.g. is canceled by the user), this fires. + */ + default void onStateTransitionFailed(STATE_TYPE toState) { } + default void onStateTransitionComplete(STATE_TYPE finalState) { } } diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java index 37b76fb90c..5279dec73b 100644 --- a/src/com/android/launcher3/touch/AllAppsSwipeController.java +++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java @@ -18,6 +18,7 @@ package com.android.launcher3.touch; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE; import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; @@ -199,8 +200,10 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER)); config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME); if (!config.userControlled) { - config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE); + config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED); } + config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED); + config.setInterpolator(ANIM_DEPTH, EMPHASIZED); } else { if (config.userControlled) { config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL)); @@ -238,8 +241,10 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT); config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER); if (!config.userControlled) { - config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE); + config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED); } + config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED); + config.setInterpolator(ANIM_DEPTH, EMPHASIZED); } else { config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC); config.setInterpolator(ANIM_WORKSPACE_FADE, diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java index f4cf21efe5..d942b7a8a5 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.java +++ b/src/com/android/launcher3/util/OnboardingPrefs.java @@ -43,13 +43,14 @@ public class OnboardingPrefs { public static final String SEARCH_ONBOARDING_COUNT = "launcher.search_onboarding_count"; public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen"; public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count"; + public static final String QSB_SEARCH_ONBOARDING_CARD_DISMISSED = "launcher.qsb_edu_dismiss"; // When adding a new key, add it here as well, to be able to reset it from Developer Options. public static final Map ALL_PREF_KEYS = Map.of( "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT }, "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT, HOTSEAT_LONGPRESS_TIP_SEEN }, "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT, - SEARCH_ONBOARDING_COUNT}, + SEARCH_ONBOARDING_COUNT, QSB_SEARCH_ONBOARDING_CARD_DISMISSED}, "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }, "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT} ); @@ -61,7 +62,8 @@ public class OnboardingPrefs { HOME_BOUNCE_SEEN, HOTSEAT_LONGPRESS_TIP_SEEN, SEARCH_KEYBOARD_EDU_SEEN, - TASKBAR_EDU_SEEN + TASKBAR_EDU_SEEN, + QSB_SEARCH_ONBOARDING_CARD_DISMISSED }) @Retention(RetentionPolicy.SOURCE) public @interface EventBoolKey {} diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java index bf35dd8d08..9daea940af 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -32,7 +32,6 @@ import com.android.launcher3.util.Themes; public class AllAppsState extends LauncherState { private static final float PARALLAX_COEFFICIENT = .125f; - private static final float WORKSPACE_SCALE_FACTOR = 0.97f; private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE; @@ -60,7 +59,8 @@ public class AllAppsState extends LauncherState { @Override public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { - return new ScaleAndTranslation(WORKSPACE_SCALE_FACTOR, NO_OFFSET, NO_OFFSET); + return new ScaleAndTranslation(launcher.getDeviceProfile().workspaceContentScale, NO_OFFSET, + NO_OFFSET); } @Override @@ -71,7 +71,7 @@ public class AllAppsState extends LauncherState { ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW .getWorkspaceScaleAndTranslation(launcher); return new ScaleAndTranslation( - WORKSPACE_SCALE_FACTOR, + launcher.getDeviceProfile().workspaceContentScale, overviewScaleAndTranslation.translationX, overviewScaleAndTranslation.translationY); } diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java index 3ca05bce80..a32ce3c558 100644 --- a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java +++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java @@ -30,39 +30,117 @@ import java.util.Set; public class CellLayoutBoard { + public static class CellType { + // The cells marked by this will be filled by 1x1 widgets and will be ignored when + // validating + public static final char IGNORE = 'x'; + // The cells marked by this will be filled by app icons + public static final char ICON = 'i'; + // Empty space + public static final char EMPTY = '-'; + // Widget that will be saved as "main widget" for easier retrieval + public static final char MAIN_WIDGET = 'm'; + // Everything else will be consider a widget + } + + public static class WidgetRect { + public char mType; + public Rect mBounds; + + WidgetRect(char type, Rect bounds) { + this.mType = type; + this.mBounds = bounds; + } + + int getSpanX() { + return mBounds.right - mBounds.left + 1; + } + + int getSpanY() { + return mBounds.top - mBounds.bottom + 1; + } + + int getCellX() { + return mBounds.left; + } + + int getCellY() { + return mBounds.bottom; + } + + boolean shouldIgnore() { + return this.mType == CellType.IGNORE; + } + + @Override + public String toString() { + return "WidgetRect type = " + mType + " bounds = " + mBounds.toString(); + } + } + + public static class IconPoint { + public Point coord; + public char mType; + + public IconPoint(Point coord, char type) { + this.coord = coord; + mType = type; + } + + public char getType() { + return mType; + } + + public void setType(char type) { + mType = type; + } + + public Point getCoord() { + return coord; + } + + public void setCoord(Point coord) { + this.coord = coord; + } + } + static final int INFINITE = 99999; - char[][] mBoard = new char[30][30]; + char[][] mWidget = new char[30][30]; - List mWidgetsRects = new ArrayList<>(); - Map mWidgetsMap = new HashMap<>(); + List mWidgetsRects = new ArrayList<>(); + Map mWidgetsMap = new HashMap<>(); - List mIconPoints = new ArrayList<>(); - Map mIconsMap = new HashMap<>(); + List mIconPoints = new ArrayList<>(); + Map mIconsMap = new HashMap<>(); Point mMain = new Point(); CellLayoutBoard() { - for (int x = 0; x < mBoard.length; x++) { - for (int y = 0; y < mBoard[0].length; y++) { - mBoard[x][y] = '-'; + for (int x = 0; x < mWidget.length; x++) { + for (int y = 0; y < mWidget[0].length; y++) { + mWidget[x][y] = CellType.EMPTY; } } } - public List getWidgets() { + public List getWidgets() { return mWidgetsRects; } + public List getIcons() { + return mIconPoints; + } + public Point getMain() { return mMain; } - public TestBoardWidget getWidgetRect(char c) { + public WidgetRect getWidgetRect(char c) { return mWidgetsMap.get(c); } - public static TestBoardWidget getWidgetRect(int x, int y, Set used, char[][] board) { + public static WidgetRect getWidgetRect(int x, int y, Set used, char[][] board) { char type = board[x][y]; Queue search = new ArrayDeque(); Point current = new Point(x, y); @@ -91,20 +169,20 @@ public class CellLayoutBoard { } } } - return new TestBoardWidget(type, widgetRect); + return new WidgetRect(type, widgetRect); } public static boolean isWidget(char type) { - return type != 'i' && type != '-'; + return type != CellType.ICON && type != CellType.EMPTY; } public static boolean isIcon(char type) { - return type == 'i'; + return type == CellType.ICON; } - private static List getRects(char[][] board) { + private static List getRects(char[][] board) { Set used = new HashSet<>(); - List widgetsRects = new ArrayList<>(); + List widgetsRects = new ArrayList<>(); for (int x = 0; x < board.length; x++) { for (int y = 0; y < board[0].length; y++) { if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) { @@ -115,12 +193,12 @@ public class CellLayoutBoard { return widgetsRects; } - private static List getIconPoints(char[][] board) { - List iconPoints = new ArrayList<>(); + private static List getIconPoints(char[][] board) { + List iconPoints = new ArrayList<>(); for (int x = 0; x < board.length; x++) { for (int y = 0; y < board[0].length; y++) { if (isIcon(board[x][y])) { - iconPoints.add(new TestBoardAppIcon(new Point(x, y), board[x][y])); + iconPoints.add(new IconPoint(new Point(x, y), board[x][y])); } } } @@ -135,18 +213,18 @@ public class CellLayoutBoard { String line = lines[y]; for (int x = 0; x < line.length(); x++) { char c = line.charAt(x); - if (c == 'm') { + if (c == CellType.MAIN_WIDGET) { board.mMain = new Point(x, y); } - if (c != '-') { - board.mBoard[x][y] = line.charAt(x); + if (c != CellType.EMPTY) { + board.mWidget[x][y] = line.charAt(x); } } } - board.mWidgetsRects = getRects(board.mBoard); + board.mWidgetsRects = getRects(board.mWidget); board.mWidgetsRects.forEach( widgetRect -> board.mWidgetsMap.put(widgetRect.mType, widgetRect)); - board.mIconPoints = getIconPoints(board.mBoard); + board.mIconPoints = getIconPoints(board.mWidget); return board; } } diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java index 5f5dfea2c3..93fbf97e99 100644 --- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java +++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java @@ -15,16 +15,13 @@ */ package com.android.launcher3.celllayout; -import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; - +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.graphics.Point; -import android.graphics.Rect; import android.util.Log; import android.view.View; -import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -34,13 +31,14 @@ import com.android.launcher3.celllayout.testcases.MoveOutReorderCase; import com.android.launcher3.celllayout.testcases.PushReorderCase; import com.android.launcher3.celllayout.testcases.ReorderTestCase; import com.android.launcher3.celllayout.testcases.SimpleReorderCase; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.tapl.Widget; +import com.android.launcher3.tapl.WidgetResizeFrame; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TaplTestsLauncher3; -import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.views.DoubleShadowBubbleTextView; +import com.android.launcher3.widget.LauncherAppWidgetHostView; import org.junit.Assume; import org.junit.Before; @@ -60,6 +58,8 @@ public class ReorderWidgets extends AbstractLauncherUiTest { private static final String TAG = ReorderWidgets.class.getSimpleName(); + TestWorkspaceBuilder mBoardBuilder; + private View getViewAt(int cellX, int cellY) { return getFromLauncher(l -> l.getWorkspace().getScreenWithId( l.getWorkspace().getScreenIdForPageIndex(0)).getChildAt(cellX, cellY)); @@ -76,6 +76,7 @@ public class ReorderWidgets extends AbstractLauncherUiTest { @Before public void setup() throws Throwable { + mBoardBuilder = new TestWorkspaceBuilder(this); TaplTestsLauncher3.initialize(this); clearHomescreen(); } @@ -86,78 +87,44 @@ public class ReorderWidgets extends AbstractLauncherUiTest { private boolean validateBoard(CellLayoutBoard board) { boolean match = true; Point cellDimensions = getCellDimensions(); - for (TestBoardWidget widgetRect: board.getWidgets()) { + for (CellLayoutBoard.WidgetRect widgetRect: board.getWidgets()) { if (widgetRect.shouldIgnore()) { continue; } View widget = getViewAt(widgetRect.getCellX(), widgetRect.getCellY()); + assertTrue("The view selected at " + board + " is not a widget", + widget instanceof LauncherAppWidgetHostView); match &= widgetRect.getSpanX() == Math.round(widget.getWidth() / (float) cellDimensions.x); match &= widgetRect.getSpanY() == Math.round(widget.getHeight() / (float) cellDimensions.y); if (!match) return match; } + for (CellLayoutBoard.IconPoint iconPoint : board.getIcons()) { + View icon = getViewAt(iconPoint.getCoord().x, iconPoint.getCoord().y); + assertTrue("The view selected at " + iconPoint.coord + " is not an Icon", + icon instanceof DoubleShadowBubbleTextView); + } return match; } - /** - * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. - */ - private void fillWithWidgets(TestBoardWidget widgetRect) { - int initX = widgetRect.getCellX(); - int initY = widgetRect.getCellY(); - for (int x = 0; x < widgetRect.getSpanX(); x++) { - for (int y = 0; y < widgetRect.getSpanY(); y++) { - int auxX = initX + x; - int auxY = initY + y; - try { - // this widgets are filling, we don't care if we can't place them - addWidgetInCell( - new TestBoardWidget('x', - new Rect(auxX, auxY, auxX, auxY)) - ); - } catch (Exception e) { - Log.d(TAG, "Unable to place filling widget at " + auxX + "," + auxY); - } - } - } - } - - private void addWidgetInCell(TestBoardWidget widgetRect) { - LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); - LauncherAppWidgetInfo item = createWidgetInfo(info, - ApplicationProvider.getApplicationContext(), true); - item.cellX = widgetRect.getCellX(); - item.cellY = widgetRect.getCellY(); - - item.spanX = widgetRect.getSpanX(); - item.spanY = widgetRect.getSpanY(); - addItemToScreen(item); - } - - private void addCorrespondingWidgetRect(TestBoardWidget widgetRect) { - if (widgetRect.mType == 'x') { - fillWithWidgets(widgetRect); - } else { - addWidgetInCell(widgetRect); - } - } - private void runTestCase(ReorderTestCase testCase) { Point mainWidgetCellPos = testCase.mStart.getMain(); - testCase.mStart.getWidgets().forEach(this::addCorrespondingWidgetRect); + mBoardBuilder.buildBoard(testCase.mStart); - mLauncher.getWorkspace() - .getWidgetAtCell(mainWidgetCellPos.x, mainWidgetCellPos.y) - .dragWidgetToWorkspace(testCase.moveMainTo.x, testCase.moveMainTo.y) - .dismiss(); // dismiss resize frame + Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.x, + mainWidgetCellPos.y); + assertNotNull(widget); + WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x, + testCase.moveMainTo.y); + resizeFrame.dismiss(); boolean isValid = false; for (CellLayoutBoard board : testCase.mEnd) { isValid |= validateBoard(board); } - assertTrue("None of the valid boards match with the current state", isValid); + assertTrue("Non of the valid boards match with the current state", isValid); } /** diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java new file mode 100644 index 0000000000..10e399dfd5 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.celllayout; + +import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; + +import android.content.ComponentName; +import android.graphics.Rect; +import android.os.Process; +import android.os.UserHandle; +import android.util.Log; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.ui.TestViewHelpers; +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; + +public class TestWorkspaceBuilder { + + private static final ComponentName APP_COMPONENT_NAME = new ComponentName( + "com.google.android.calculator", "com.android.calculator2.Calculator"); + + public AbstractLauncherUiTest mTest; + + private UserHandle mMyUser; + + public TestWorkspaceBuilder(AbstractLauncherUiTest test) { + mTest = test; + mMyUser = Process.myUserHandle(); + } + + private static final String TAG = "CellLayoutBoardBuilder"; + + /** + * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. + */ + private void fillWithWidgets(CellLayoutBoard.WidgetRect widgetRect) { + int initX = widgetRect.getCellX(); + int initY = widgetRect.getCellY(); + for (int x = initX; x < initX + widgetRect.getSpanX(); x++) { + for (int y = initY; y < initY + widgetRect.getSpanY(); y++) { + try { + // this widgets are filling, we don't care if we can't place them + addWidgetInCell( + new CellLayoutBoard.WidgetRect(CellLayoutBoard.CellType.IGNORE, + new Rect(x, y, x, y)) + ); + } catch (Exception e) { + Log.d(TAG, "Unable to place filling widget at " + x + "," + y); + } + } + } + } + + private void addWidgetInCell(CellLayoutBoard.WidgetRect widgetRect) { + LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(mTest, false); + LauncherAppWidgetInfo item = createWidgetInfo(info, + ApplicationProvider.getApplicationContext(), true); + + item.cellX = widgetRect.getCellX(); + item.cellY = widgetRect.getCellY(); + item.spanX = widgetRect.getSpanX(); + item.spanY = widgetRect.getSpanY(); + mTest.addItemToScreen(item); + } + + private void addIconInCell(CellLayoutBoard.IconPoint iconPoint) { + AppInfo appInfo = new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser, + AppInfo.makeLaunchIntent(APP_COMPONENT_NAME)); + + appInfo.cellX = iconPoint.getCoord().x; + appInfo.cellY = iconPoint.getCoord().y; + appInfo.minSpanY = appInfo.minSpanX = appInfo.spanX = appInfo.spanY = 1; + appInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + appInfo.componentName = APP_COMPONENT_NAME; + + mTest.addItemToScreen(new WorkspaceItemInfo(appInfo)); + } + + private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect) { + if (widgetRect.mType == 'x') { + fillWithWidgets(widgetRect); + } else { + addWidgetInCell(widgetRect); + } + } + + public void buildBoard(CellLayoutBoard board) { + board.getWidgets().forEach(this::addCorrespondingWidgetRect); + board.getIcons().forEach(this::addIconInCell); + } +} diff --git a/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java index 1326389ab7..94e55cfbde 100644 --- a/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java +++ b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java @@ -19,6 +19,10 @@ import android.graphics.Point; import java.util.Map; +/** + * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character + * in the board mean refer to {@code CellType} + */ public class FullReorderCase { /** 5x5 Test diff --git a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java index 1701390268..a222d3d551 100644 --- a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java +++ b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java @@ -19,6 +19,10 @@ import android.graphics.Point; import java.util.Map; +/** + * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character + * in the board mean refer to {@code CellType} + */ public class MoveOutReorderCase { /** 5x5 Test diff --git a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java index bb8d5e9631..e16ff42a8b 100644 --- a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java +++ b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java @@ -19,6 +19,10 @@ import android.graphics.Point; import java.util.Map; +/** + * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character + * in the board mean refer to {@code CellType} + */ public class PushReorderCase { /** 5x5 Test diff --git a/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java index 30269a0f5d..546c48bccb 100644 --- a/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java +++ b/tests/src/com/android/launcher3/celllayout/testcases/SimpleReorderCase.java @@ -19,6 +19,10 @@ import android.graphics.Point; import java.util.Map; +/** + * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character + * in the board mean refer to {@code CellType} + */ public class SimpleReorderCase { /** 5x5 Test diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 304153f53f..a66b09a87d 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -319,7 +319,7 @@ public abstract class AbstractLauncherUiTest { /** * Adds {@param item} on the homescreen on the 0th screen */ - protected void addItemToScreen(ItemInfo item) { + public void addItemToScreen(ItemInfo item) { WidgetUtils.addItemToScreen(item, mTargetContext); resetLoaderState();