Initial commit of new split screen work.
TODO:
* Extract out common elements in TaskShortcutFactory.MultiWindowFactory
for commonalities between new and old way of
invoking split screen
* Integrate with WM APIs (b/182002789)
* Write tests for SplitSelectStateController
Bug: 181704764
Change-Id: Ice35adb4ea82897f5e2433dc9b93a549f3d511b5
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index cd64a94..55400a7 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -19,6 +19,14 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:alpha=".8"
+ android:visibility="gone" />
+
<com.android.quickstep.fallback.RecentsDragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index fe57e9b..394e880 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -15,6 +15,14 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:alpha=".8"
+ android:visibility="gone" />
+
<com.android.quickstep.views.LauncherRecentsView
android:id="@+id/overview_panel"
android:layout_width="match_parent"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2a24624..755bce8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -39,6 +39,7 @@
<dimen name="overview_grid_bottom_margin">90dp</dimen>
<dimen name="overview_grid_side_margin">54dp</dimen>
<dimen name="overview_grid_row_spacing">42dp</dimen>
+ <dimen name="split_placeholder_size">110dp</dimen>
<dimen name="recents_page_spacing">16dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 9a4487c..0764bb3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -22,8 +22,6 @@
<string name="derived_app_name" translatable="false">Quickstep</string>
<!-- Options for recent tasks -->
- <!-- Title for an option to enter split screen mode for a given app -->
- <string name="recent_task_option_split_screen">Split screen</string>
<!-- Title for an option to keep an app pinned to the screen until it is unpinned -->
<string name="recent_task_option_pin">Pin</string>
<!-- Title for an option to enter freeform mode for a given app -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index cdfd1a2..20a645e 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -58,8 +58,10 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -215,7 +217,12 @@
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
- ((RecentsView) getOverviewPanel()).init(mActionsView);
+ SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+ RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+ splitPlaceholderView.init(
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this))
+ );
+ overviewPanel.init(mActionsView, splitPlaceholderView);
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
addTaskbarIfNecessary();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 0f13ef9..bedaefa 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -32,6 +32,7 @@
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
@@ -44,6 +45,7 @@
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -105,7 +107,12 @@
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
- setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+ FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_TRANSLATION, TASK_SECONDARY_TRANSLATION, mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, taskViewsFloat,
+ toState.getOverviewSecondaryTranslation(mLauncher),
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index c9de662..750f673 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,11 +17,14 @@
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
import android.annotation.TargetApi;
import android.os.Build;
@@ -77,11 +80,26 @@
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
+ // Create or dismiss split screen select animations
+ LauncherState currentState = mLauncher.getStateManager().getState();
+ if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+ }
+
setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
+ /**
+ * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ */
+ private boolean isSplitSelectionState(@NonNull LauncherState toState) {
+ return toState == OVERVIEW_SPLIT_SELECT;
+ }
+
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
LauncherState state) {
float clearAllButtonAlpha = (state.getVisibleElements(mLauncher) & CLEAR_ALL_BUTTON) != 0
@@ -93,6 +111,11 @@
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
+
+ float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
+ 1 : 0;
+ propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
+ splitPlaceholderAlpha, LINEAR);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 1f68a04..372784a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -175,4 +175,12 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewModalTaskState(id);
}
+
+ /**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new SplitScreenSelectState(id);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
new file mode 100644
index 0000000..722d74a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.uioverrides.states;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+public class SplitScreenSelectState extends OverviewState {
+ public SplitScreenSelectState(int id) {
+ super(id);
+ }
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ return SPLIT_PLACHOLDER_VIEW;
+ }
+
+ @Override
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
+ return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3d68d64..d3ed791 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -63,7 +63,9 @@
import com.android.quickstep.fallback.RecentsDragLayer;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -105,8 +107,14 @@
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
+ SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+ splitPlaceholderView.init(
+ new SplitSelectStateController(
+ SystemUiProxy.INSTANCE.get(this))
+ );
+
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView);
+ mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 8636130..cd13200 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -36,11 +36,16 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -57,11 +62,18 @@
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
- public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+ public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
+ DeviceProfile deviceProfile) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+ if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+ FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ continue;
+ }
+
if (shortcut != null) {
shortcuts.add(shortcut);
}
@@ -91,6 +103,18 @@
return shortcuts;
}
+
+ public static void addSplitOptions(List<SystemShortcut> outShortcuts,
+ BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+ PagedOrientationHandler orientationHandler =
+ taskView.getRecentsView().getPagedOrientationHandler();
+ List<SplitPositionOption> positions =
+ orientationHandler.getSplitPositionOptions(deviceProfile);
+ for (SplitPositionOption option : positions) {
+ outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option));
+ }
+ }
+
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
return new TaskOverlay(thumbnailView);
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index d81f07f..c06e9a9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -34,11 +34,13 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -58,7 +60,6 @@
* Represents a system shortcut that can be shown for a recent task.
*/
public interface TaskShortcutFactory {
-
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
@@ -93,6 +94,23 @@
}
}
+ class SplitSelectSystemShortcut extends SystemShortcut {
+ private final TaskView mTaskView;
+ private SplitPositionOption mSplitPositionOption;
+ public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
+ SplitPositionOption option) {
+ super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+ mTaskView = taskView;
+ mSplitPositionOption = option;
+ setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+
+ @Override
+ public void onClick(View view) {
+ mTaskView.initiateSplitSelect(mSplitPositionOption);
+ }
+ }
+
class MultiWindowSystemShortcut extends SystemShortcut {
private Handler mHandler;
@@ -211,6 +229,16 @@
}
@Override
+ public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ SystemShortcut shortcut = super.getShortcut(activity, taskView);
+ if (FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ // Disable if there's only one recent app for split screen
+ shortcut.setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+ return shortcut;
+ }
+
+ @Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
final ActivityCompat act = new ActivityCompat(activity);
final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 2feeffa..7a428ce 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -37,6 +38,7 @@
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
@@ -295,6 +297,93 @@
}
}
+ /**
+ * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
+ * if needed
+ *
+ * We could manually try to animate the just the bounds for the leashes we get back, but we try
+ * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
+ * us.
+ *
+ * First you have to call TVS#setPreview() to indicate which leash it will operate one
+ * Then operations happen in TVS#apply() on each frame callback.
+ *
+ * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
+ * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
+ * device is considered in multiWindowMode and things like insets and stuff change
+ * and calculations have to be adjusted in the animations for that
+ */
+ public static void composeRecentsSplitLaunchAnimator(@NonNull AnimatorSet anim,
+ @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
+ @NonNull StateManager stateManager, @NonNull DepthController depthController,
+ int targetStage) {
+ PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ boolean isRunningTask = v.isRunningTask();
+ TransformParams params = null;
+ TaskViewSimulator tvs = null;
+ RecentsView recentsView = v.getRecentsView();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+ params = recentsView.getLiveTileParams();
+ tvs = recentsView.getLiveTileTaskViewSimulator();
+ }
+
+ boolean inLiveTileMode =
+ ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
+ final RemoteAnimationTargets targets =
+ new RemoteAnimationTargets(appTargets, wallpaperTargets,
+ inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+
+ if (params == null) {
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ targets.addReleaseCheck(applier);
+
+ params = new TransformParams()
+ .setSyncTransactionApplier(applier)
+ .setTargetSet(targets);
+ }
+
+ Rect crop = new Rect();
+ Context context = v.getContext();
+ DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ if (tvs == null && targets.apps.length > 0) {
+ tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
+ tvs.setDp(dp);
+
+ // RecentsView never updates the display rotation until swipe-up so the value may
+ // be stale. Use the display value instead.
+ int displayRotation = DisplayController.getDefaultDisplay(recentsView.getContext())
+ .getInfo().rotation;
+ tvs.getOrientationState().update(displayRotation, displayRotation);
+
+ tvs.setPreview(targets.apps[targets.apps.length - 1]);
+ tvs.fullScreenProgress.value = 0;
+ tvs.recentsViewScale.value = 1;
+// tvs.setScroll(startScroll);
+
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.2f));
+ }
+
+ TaskViewSimulator topMostSimulator = null;
+
+ if (tvs != null) {
+ out.setFloat(tvs.fullScreenProgress,
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvs.recentsViewScale,
+ AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setInt(tvs, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
+
+ TaskViewSimulator finalTsv = tvs;
+ TransformParams finalParams = params;
+ out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ topMostSimulator = tvs;
+ }
+
+ anim.play(out.buildAnim());
+ }
+
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 13f6137..02fd5bb 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -34,6 +34,7 @@
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -56,8 +57,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
new file mode 100644
index 0000000..d9154ed
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.quickstep.util;
+
+import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.launcher3.WrappedAnimationRunnerImpl;
+import com.android.launcher3.WrappedLauncherAnimationRunner;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Represent data needed for the transient state when user has selected one app for split screen
+ * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
+ */
+public class SplitSelectStateController {
+
+ private final SystemUiProxy mSystemUiProxy;
+ private TaskView mInitialTaskView;
+ private SplitPositionOption mInitialPosition;
+
+ public SplitSelectStateController(SystemUiProxy systemUiProxy) {
+ mSystemUiProxy = systemUiProxy;
+ }
+
+ /**
+ * To be called after first task selected
+ */
+ public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption) {
+ mInitialTaskView = taskView;
+ mInitialPosition = positionOption;
+ }
+
+ /**
+ * To be called after second task selected
+ */
+ public void setSecondTaskId(TaskView taskView) {
+ // Assume initial mInitialTaskId is for top/left part of screen
+ WrappedAnimationRunnerImpl initialSplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ mInitialTaskView, 0);
+ WrappedAnimationRunnerImpl secondarySplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ taskView, 1);
+ RemoteAnimationRunnerCompat initialSplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ RemoteAnimationRunnerCompat secondarySplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
+ ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
+ mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
+ mInitialPosition.mStagePosition,
+ /*null*/ initialOptions.toBundle());
+ Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
+ mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
+ compliment.second,
+ /*null*/ secondaryOptions.toBundle());
+ // After successful launch, call resetState
+ resetState();
+ }
+
+ @Nullable
+ public SplitPositionOption getActiveSplitPositionOption() {
+ return mInitialPosition;
+ }
+
+ /**
+ * @return the opposite stage and position from the {@param position} provided as first and
+ * second object, respectively
+ * Ex. If position is has stage = Main and position = Top/Left, this will return
+ * Pair(stage=Side, position=Bottom/Left)
+ */
+ private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
+ // Right now this is as simple as flipping between 0 and 1
+ int complimentStageType = position.mStageType ^ 1;
+ int complimentStagePosition = position.mStagePosition ^ 1;
+ return new Pair<>(complimentStageType, complimentStagePosition);
+ }
+
+ /**
+ * Remote animation runner for animation to launch an app.
+ */
+ private class SplitLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
+
+ private final TaskView mV;
+ private final int mTargetState;
+
+ SplitLaunchAnimationRunner(TaskView v, int targetState) {
+ mV = v;
+ mTargetState = targetState;
+ }
+
+ @Override
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ LauncherAnimationRunner.AnimationResult result) {
+ AnimatorSet anim = new AnimatorSet();
+ BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
+ TaskViewUtils.composeRecentsSplitLaunchAnimator(anim, mV,
+ appTargets, wallpaperTargets, true, activity.getStateManager(),
+ activity.getDepthController(), mTargetState);
+ result.setAnimation(anim, activity);
+ }
+ }
+
+
+ /**
+ * To be called if split select was cancelled
+ */
+ public void resetState() {
+ mInitialTaskView = null;
+ mInitialPosition = null;
+ }
+
+ public boolean isSplitSelectActive() {
+ return mInitialTaskView != null;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index df1229b..6e8a5f1 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -104,6 +104,7 @@
public final AnimatedFloat recentsViewScale = new AnimatedFloat();
public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
+ public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat gridProgress = new AnimatedFloat();
private final ScrollState mScrollState = new ScrollState();
@@ -347,6 +348,8 @@
mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewSecondaryTranslation.value);
+ mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+ recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
// Crop rect is the inverse of thumbnail matrix
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index ceb343d..9d31190 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -86,8 +86,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setContentAlpha(0);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b963025..3c5e403 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
@@ -48,6 +49,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
@@ -114,6 +116,7 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.AnimatedFloat;
@@ -211,11 +214,17 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
new FloatProperty<RecentsView>("taskSecondaryTranslation") {
@Override
public void setValue(RecentsView recentsView, float v) {
- recentsView.setTaskViewsSecondaryTranslation(v);
+ recentsView.setTaskViewsResistanceTranslation(v);
}
@Override
@@ -224,6 +233,25 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
+ public static final FloatProperty<RecentsView> TASK_PRIMARY_TRANSLATION =
+ new FloatProperty<RecentsView>("taskPrimaryTranslation") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ recentsView.setTaskViewsPrimaryTranslation(v);
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mTaskViewsPrimaryTranslation;
+ }
+ };
+
/** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
new FloatProperty<RecentsView>("recentsScale") {
@@ -234,7 +262,8 @@
view.mLastComputedTaskPushOutDistance = null;
view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
view.updatePageOffsets();
- view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsPrimaryTranslation(view.mTaskViewsPrimaryTranslation);
}
@Override
@@ -306,6 +335,7 @@
private float mAdjacentPageOffset = 0;
private float mTaskViewsSecondaryTranslation = 0;
+ private float mTaskViewsPrimaryTranslation = 0;
// Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
private float mGridProgress = 0;
private boolean mShowAsGrid;
@@ -427,6 +457,28 @@
private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
private Layout mEmptyTextLayout;
+ /**
+ * Placeholder view indicating where the first split screen selected app will be placed
+ */
+ private SplitPlaceholderView mSplitPlaceholderView;
+ /**
+ * The first task that split screen selection was initiated with. When split select state is
+ * initialized, we create a
+ * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
+ * don't actually remove the task since the user might back out. As such, we also ensure this
+ * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+ */
+ private TaskView mSplitHiddenTaskView;
+ /**
+ * Keeps track of the index of the TaskView that split screen was initialized with so we know
+ * where to insert it back into list of taskViews in case user backs out of entering split
+ * screen.
+ * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
+ * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
+ * removed from recentsView
+ */
+ private int mSplitHiddenTaskViewIndex;
+
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
private OverviewActionsView mActionsView;
@@ -582,9 +634,19 @@
loadVisibleTaskData();
}
- public void init(OverviewActionsView actionsView) {
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ mSplitPlaceholderView = splitPlaceholderView;
+
+ }
+
+ public SplitPlaceholderView getSplitPlaceholder() {
+ return mSplitPlaceholderView;
+ }
+
+ public boolean isSplitSelectionActive() {
+ return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
}
@Override
@@ -628,8 +690,9 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- // Clear the task data for the removed child if it was visible
- if (child instanceof TaskView) {
+ // Clear the task data for the removed child if it was visible unless it's the initial
+ // taskview for entering split screen, we only pretend to dismiss the task
+ if (child instanceof TaskView && child != mSplitHiddenTaskView) {
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
@@ -707,6 +770,9 @@
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
+ if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+ cancelSplitSelect(false);
+ }
}
}
@@ -1764,7 +1830,7 @@
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
FloatProperty<TaskView> secondaryViewTranslate =
- taskView.getDismissTaskTranslationProperty();
+ taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
@@ -1827,10 +1893,23 @@
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
}
+
+ // Additional offset for fake landscape, if the pinning happens to the right or
+ // left, we need to scroll all the tasks away from the direction of the splaceholder
+ // view
+ if (isSplitSelectionActive()) {
+ int splitPosition = getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = mOrientationHandler
+ .getSplitTranslationDirectionFactor(splitPosition);
+ int splitOffset = mOrientationHandler.getSplitAnimationTranslation(
+ mSplitPlaceholderView.getHeight(), mActivity.getDeviceProfile());
+ offset += direction * splitOffset;
+ }
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
FloatProperty translationProperty = child instanceof TaskView
- ? ((TaskView) child).getFillDismissGapTranslationProperty()
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
: mOrientationHandler.getPrimaryViewTranslate();
ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -1908,6 +1987,12 @@
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
}
resetTaskVisuals();
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // We want to keep the tasks translations in this temporary state
+ // after resetting the rest above
+ setTaskViewsResistanceTranslation(mTaskViewsSecondaryTranslation);
+ setTaskViewsPrimaryTranslation(mTaskViewsPrimaryTranslation);
+ }
mPendingAnimation = null;
}
});
@@ -2299,7 +2384,7 @@
return distanceToOffscreen * offsetProgress;
}
- private void setTaskViewsSecondaryTranslation(float translation) {
+ private void setTaskViewsResistanceTranslation(float translation) {
mTaskViewsSecondaryTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView task = getTaskViewAt(i);
@@ -2308,6 +2393,15 @@
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
+ private void setTaskViewsPrimaryTranslation(float translation) {
+ mTaskViewsPrimaryTranslation = translation;
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView task = getTaskViewAt(i);
+ task.getPrimaryDismissTranslationProperty().set(task, translation / getScaleY());
+ }
+ mLiveTileTaskViewSimulator.recentsViewPrimaryTranslation.value = translation;
+ }
+
/**
* TODO: Do not assume motion across X axis for adjacent page
*/
@@ -2325,6 +2419,111 @@
}
}
+ public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
+ mSplitHiddenTaskView = taskView;
+ mSplitPlaceholderView.getSplitController().setInitialTaskSelect(taskView,
+ splitPositionOption);
+ mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+ }
+
+ public PendingAnimation createSplitSelectInitAnimation() {
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+ }
+
+ public void confirmSplitSelect(TaskView taskView) {
+ mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
+ resetTaskVisuals();
+ setTranslationY(0);
+ }
+
+ public PendingAnimation cancelSplitSelect(boolean animate) {
+ mSplitPlaceholderView.getSplitController().resetState();
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ PendingAnimation pendingAnim = new PendingAnimation(duration);
+ if (!animate) {
+ resetFromSplitSelectionState();
+ return pendingAnim;
+ }
+
+ addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
+ mSplitHiddenTaskView.getLayoutParams());
+ mSplitHiddenTaskView.setAlpha(0);
+ int[] oldScroll = new int[getChildCount()];
+ getPageScrolls(oldScroll, false,
+ view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView);
+
+ // x is correct, y is before tasks move up
+ int[] locationOnScreen = mSplitHiddenTaskView.getLocationOnScreen();
+ int[] newScroll = new int[getChildCount()];
+ getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC);
+
+ boolean needsCurveUpdates = false;
+ for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child == mSplitHiddenTaskView) {
+
+ int left = newScroll[i] + getPaddingStart();
+ int topMargin = mSplitHiddenTaskView.getThumbnailTopMargin();
+ int top = -mSplitHiddenTaskView.getHeight() - locationOnScreen[1];
+ mSplitHiddenTaskView.layout(left, top,
+ left + mSplitHiddenTaskView.getWidth(),
+ top + mSplitHiddenTaskView.getHeight());
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, TRANSLATION_Y,
+ -top + mSplitPlaceholderView.getHeight() - topMargin));
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1));
+ } else {
+ // If insertion is on last index (furthest from clear all), we directly add the view
+ // else we translate all views to the right of insertion index further right,
+ // ignore views to left
+ int scrollDiff = newScroll[i] - oldScroll[i];
+ if (scrollDiff != 0) {
+ FloatProperty translationProperty = child instanceof TaskView
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
+ : mOrientationHandler.getPrimaryViewTranslate();
+
+ ResourceProvider rp = DynamicResource.provider(mActivity);
+ SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
+ .setDampingRatio(
+ rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
+ pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
+ .setDuration(duration), ACCEL, sp);
+ needsCurveUpdates = true;
+ }
+ }
+ }
+
+ if (needsCurveUpdates) {
+ pendingAnim.addOnFrameCallback(this::updateCurveProperties);
+ }
+
+ pendingAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ resetFromSplitSelectionState();
+ }
+ });
+
+ return pendingAnim;
+ }
+
+ private void resetFromSplitSelectionState() {
+ mSplitHiddenTaskView.setTranslationY(0);
+ int pageToSnapTo = mCurrentPage;
+ if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
+ pageToSnapTo += 1;
+ } else {
+ pageToSnapTo = mSplitHiddenTaskViewIndex;
+ }
+ snapToPageImmediately(pageToSnapTo);
+ onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
+ resetTaskVisuals();
+ mSplitHiddenTaskView = null;
+ mSplitHiddenTaskViewIndex = -1;
+ }
+
private void updateDeadZoneRects() {
// Get the deadzone rect surrounding the clear all button to not dismiss overview to home
mClearAllButtonDeadZoneRect.setEmpty();
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
new file mode 100644
index 0000000..fb9be81
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.quickstep.util.SplitSelectStateController;
+
+public class SplitPlaceholderView extends View {
+
+ public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
+ new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
+ @Override
+ public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
+ splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE);
+ splitPlaceholderView.setAlpha(v);
+ }
+
+ @Override
+ public Float get(SplitPlaceholderView splitPlaceholderView) {
+ return splitPlaceholderView.getAlpha();
+ }
+ };
+
+ private SplitSelectStateController mSplitController;
+
+ public SplitPlaceholderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void init(SplitSelectStateController controller) {
+ this.mSplitController = controller;
+ }
+
+ public SplitSelectStateController getSplitController() {
+ return mSplitController;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index fe7ece2..a5b7a5b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -186,7 +186,8 @@
mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskName.setOnClickListener(v -> close(true));
- TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
+ TaskOverlayFactory.getEnabledShortcuts(taskView, mActivity.getDeviceProfile())
+ .forEach(this::addMenuOption);
}
private void addMenuOption(SystemShortcut menuOption) {
@@ -196,6 +197,8 @@
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
+ menuOptionView.setEnabled(menuOption.isEnabled());
+ menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
menuOptionView.setOnClickListener(view -> {
if (LIVE_TILE.get()) {
RecentsView recentsView = mTaskView.getRecentsView();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index eace0f8..cd8ea76 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -28,6 +28,7 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -84,6 +85,7 @@
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
@@ -345,7 +347,12 @@
});
anim.start();
} else {
- launchTaskAnimated();
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // User tapped to select second split screen app
+ getRecentsView().confirmSplitSelect(this);
+ } else {
+ launchTaskAnimated();
+ }
}
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
@@ -591,6 +598,11 @@
}
private boolean showTaskMenu() {
+ if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // Don't show menu when selecting second split screen app
+ return true;
+ }
+
if (!getRecentsView().isClearAllHidden()) {
getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
} else {
@@ -619,6 +631,10 @@
}
}
+ public int getThumbnailTopMargin() {
+ return (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ }
+
public void setOrientationState(RecentsOrientedState orientationState) {
PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
@@ -998,12 +1014,12 @@
return Utilities.mapRange(progress, 0, endTranslation);
}
- public FloatProperty<TaskView> getFillDismissGapTranslationProperty() {
+ public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
- public FloatProperty<TaskView> getDismissTaskTranslationProperty() {
+ public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
@@ -1081,7 +1097,8 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
info.addAction(s.createAccessibilityAction(context));
}
@@ -1113,7 +1130,8 @@
return true;
}
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
if (s.hasHandlerForAction(action)) {
s.onClick(this);
return true;
@@ -1274,6 +1292,12 @@
mSnapshotView.setOverlayEnabled(overlayEnabled);
}
+ public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
+ RecentsView rv = getRecentsView();
+ getMenuView().close(false);
+ rv.initiateSplitSelect(this, splitPositionOption);
+ }
+
/**
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
*/
diff --git a/quickstep/res/drawable/ic_split_screen.xml b/res/drawable/ic_split_screen.xml
similarity index 100%
rename from quickstep/res/drawable/ic_split_screen.xml
rename to res/drawable/ic_split_screen.xml
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 351182d..0600cae 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,6 +38,13 @@
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
+ <!-- Options for recent tasks -->
+ <!-- Title for an option to enter split screen mode for a given app -->
+ <string name="recent_task_option_split_screen">Split screen</string>
+ <string translatable="false" name="split_screen_position_top">Pin to top</string>
+ <string translatable="false" name="split_screen_position_left">Pin to left</string>
+ <string translatable="false" name="split_screen_position_right">Pin to right</string>
+
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
<string name="long_press_widget_to_add">Touch & hold to move a widget.</string>
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 21c40ef..aa97450 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -60,6 +61,7 @@
public static final int TASKBAR = 1 << 7;
public static final int CLEAR_ALL_BUTTON = 1 << 8;
public static final int WORKSPACE_PAGE_INDICATOR = 1 << 9;
+ public static final int SPLIT_PLACHOLDER_VIEW = 1 << 10;
/** Mask of all the items that are contained in the apps view. */
public static final int APPS_VIEW_ITEM_MASK =
@@ -126,6 +128,8 @@
OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
public static final LauncherState BACKGROUND_APP =
OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
+ public static final LauncherState OVERVIEW_SPLIT_SELECT =
+ OverviewState.newSplitSelectState(OVERVIEW_SPLIT_SELECT_ORDINAL);
public final int ordinal;
@@ -241,6 +245,14 @@
}
/**
+ * For this state, how much additional vertical translation there should be for each of the
+ * child TaskViews.
+ */
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ return 0;
+ }
+
+ /**
* The amount of blur and wallpaper zoom to apply to the background of either the app
* or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
*
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 3657e89..48e41d5 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -205,6 +205,9 @@
"ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
+ public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
+ "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 577fe4a..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -45,6 +45,11 @@
protected final T mTarget;
protected final ItemInfo mItemInfo;
+ /**
+ * Indicates if it's invokable or not through some disabled UI
+ */
+ private boolean isEnabled = true;
+
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
mIconResId = iconResId;
mLabelResId = labelResId;
@@ -83,6 +88,14 @@
mAccessibilityActionId, context.getText(mLabelResId));
}
+ public void setEnabled(boolean enabled) {
+ isEnabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
public boolean hasHandlerForAction(int action) {
return mAccessibilityActionId == action;
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 7cb6e34..f34bff6 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -32,6 +32,7 @@
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
public static final int HINT_STATE_ORDINAL = 7;
+ public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 8;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static final String SEQUENCE_MAIN = "Main";
public static final String SEQUENCE_TIS = "TIS";
@@ -55,6 +56,8 @@
return "Background";
case HINT_STATE_ORDINAL:
return "Hint";
+ case OVERVIEW_SPLIT_SELECT_ORDINAL:
+ return "OverviewSplitSelect";
default:
return "Unknown";
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 8a64f3d..c1cf0c8 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -21,6 +21,10 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -36,8 +40,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class LandscapePagedViewHandler implements PagedOrientationHandler {
@@ -212,6 +221,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return thumbnailView.getMeasuredWidth() + x;
}
@@ -282,4 +305,23 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return rect.left;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // Add left/right options where left => position top, right => position bottom
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ return options;
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile) {
+ return primary;
+ }
}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index e1cec87..fcfa205 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -32,6 +32,10 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+import java.util.List;
/**
* Abstraction layer to separate horizontal and vertical specific implementations
@@ -75,6 +79,8 @@
int getScrollOffsetEnd(View view, Rect insets);
int getPrimaryTranslationDirectionFactor();
int getSecondaryTranslationDirectionFactor();
+ int getSplitTranslationDirectionFactor(@StagePosition int stagePosition);
+ int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp);
ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
@@ -95,6 +101,9 @@
int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
+ List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+ FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile);
// The following are only used by TaskViewTouchHandler.
/** @return Either VERTICAL or HORIZONTAL. */
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index bcaf5f4..2bc2dc7 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -19,6 +19,10 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -34,8 +38,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class PortraitPagedViewHandler implements PagedOrientationHandler {
@@ -208,6 +217,23 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ if (dp.isLandscape) {
+ return translationOffset;
+ }
+ return 0;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return x;
}
@@ -277,4 +303,35 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return dp.heightPx - rect.bottom;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // TODO: Add in correct icons
+ if (dp.isLandscape) { // or seascape
+ // Add left/right options
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ } else {
+ // Only add top option
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_top,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ }
+ return options;
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile dp) {
+ if (dp.isLandscape) { // or seascape
+ return primary;
+ } else {
+ return secondary;
+ }
+ }
}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 54af029..b5252f7 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -17,6 +17,10 @@
package com.android.launcher3.touch;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -25,7 +29,12 @@
import android.view.View;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@@ -35,6 +44,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public boolean getRecentsRtlSetting(Resources resources) {
return Utilities.isRtl(resources);
}
@@ -71,6 +94,19 @@
return dp.widthPx - rect.right;
}
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // Add left/right options where left => position bottom, right => position top
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ return options;
+ }
+
/* ---------- The following are only used by TaskViewTouchHandler. ---------- */
@Override
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
new file mode 100644
index 0000000..573c8bd
--- /dev/null
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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.util;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+public final class SplitConfigurationOptions {
+
+ ///////////////////////////////////
+ // Taken from
+ // frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+ /**
+ * Stage position isn't specified normally meaning to use what ever it is currently set to.
+ */
+ public static final int STAGE_POSITION_UNDEFINED = -1;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+ @Retention(SOURCE)
+ @IntDef({STAGE_POSITION_UNDEFINED, STAGE_POSITION_TOP_OR_LEFT, STAGE_POSITION_BOTTOM_OR_RIGHT})
+ public @interface StagePosition {}
+
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ public static final int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ */
+ public static final int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ */
+ public static final int STAGE_TYPE_SIDE = 1;
+
+ @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE})
+ public @interface StageType {}
+ ///////////////////////////////////
+
+ public static class SplitPositionOption {
+ public final int mIconResId;
+ public final int mTextResId;
+ @StagePosition
+ public final int mStagePosition;
+
+ @StageType
+ public final int mStageType;
+
+ public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
+ mIconResId = iconResId;
+ mTextResId = textResId;
+ mStagePosition = stagePosition;
+ mStageType = stageType;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 5b33f18..e413d7f 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -58,7 +58,7 @@
Preconditions.assertUIThread();
Handler handler = new Handler();
- // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+ // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'.
// Create a different copy to use on the background thread.
LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index da5a94f..e85e505 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -49,4 +49,11 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new OverviewState(id);
+ }
}