| /* |
| * 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 com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP; |
| import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM; |
| |
| import static java.lang.annotation.RetentionPolicy.SOURCE; |
| |
| import android.content.Intent; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.view.View; |
| |
| import androidx.annotation.IntDef; |
| |
| import com.android.launcher3.logging.StatsLogManager; |
| import com.android.launcher3.model.data.ItemInfo; |
| |
| 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 iconResId; |
| public final int textResId; |
| @StagePosition |
| public final int stagePosition; |
| |
| @StageType |
| public final int mStageType; |
| |
| public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) { |
| this.iconResId = iconResId; |
| this.textResId = textResId; |
| this.stagePosition = stagePosition; |
| mStageType = stageType; |
| } |
| } |
| |
| /** |
| * NOTE: Engineers complained about too little ambiguity in the last survey, so there is a class |
| * with the same name/functionality in wm.shell.util (which launcher3 cannot be built against) |
| * |
| * If you make changes here, consider making the same changes there |
| * TODO(b/254378592): We really need to consolidate this |
| */ |
| public static class SplitBounds { |
| public final Rect leftTopBounds; |
| public final Rect rightBottomBounds; |
| /** This rect represents the actual gap between the two apps */ |
| public final Rect visualDividerBounds; |
| // This class is orientation-agnostic, so we compute both for later use |
| public final float topTaskPercent; |
| public final float leftTaskPercent; |
| public final float dividerWidthPercent; |
| public final float dividerHeightPercent; |
| public final int snapPosition; |
| |
| /** |
| * If {@code true}, that means at the time of creation of this object, the |
| * split-screened apps were vertically stacked. This is useful in scenarios like |
| * rotation where the bounds won't change, but this variable can indicate what orientation |
| * the bounds were originally in |
| */ |
| public final boolean appsStackedVertically; |
| /** |
| * If {@code true}, that means at the time of creation of this object, the phone was in |
| * seascape orientation. This is important on devices with insets, because they do not split |
| * evenly -- one of the insets must be slightly larger to account for the inset. |
| * From landscape, it is the leftTop task that expands slightly. |
| * From seascape, it is the rightBottom task that expands slightly. |
| */ |
| public final boolean initiatedFromSeascape; |
| public final int leftTopTaskId; |
| public final int rightBottomTaskId; |
| |
| public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, |
| int rightBottomTaskId, int snapPosition) { |
| this.leftTopBounds = leftTopBounds; |
| this.rightBottomBounds = rightBottomBounds; |
| this.leftTopTaskId = leftTopTaskId; |
| this.rightBottomTaskId = rightBottomTaskId; |
| this.snapPosition = snapPosition; |
| |
| if (rightBottomBounds.top > leftTopBounds.top) { |
| // vertical apps, horizontal divider |
| this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, |
| leftTopBounds.right, rightBottomBounds.top); |
| appsStackedVertically = true; |
| initiatedFromSeascape = false; |
| } else { |
| // horizontal apps, vertical divider |
| this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, |
| rightBottomBounds.left, leftTopBounds.bottom); |
| appsStackedVertically = false; |
| // The following check is unreliable on devices without insets |
| // (initiatedFromSeascape will always be set to false.) This happens to be OK for |
| // all our current uses, but should be refactored. |
| // TODO: Create a more reliable check, or refactor how splitting works on devices |
| // with insets. |
| if (rightBottomBounds.width() > leftTopBounds.width()) { |
| initiatedFromSeascape = true; |
| } else { |
| initiatedFromSeascape = false; |
| } |
| } |
| |
| float totalWidth = rightBottomBounds.right - leftTopBounds.left; |
| float totalHeight = rightBottomBounds.bottom - leftTopBounds.top; |
| leftTaskPercent = leftTopBounds.width() / totalWidth; |
| topTaskPercent = leftTopBounds.height() / totalHeight; |
| dividerWidthPercent = visualDividerBounds.width() / totalWidth; |
| dividerHeightPercent = visualDividerBounds.height() / totalHeight; |
| } |
| |
| @Override |
| public String toString() { |
| return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" |
| + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" |
| + "Divider: " + visualDividerBounds + "\n" |
| + "AppsVertical? " + appsStackedVertically + "\n" |
| + "snapPosition: " + snapPosition; |
| } |
| } |
| |
| public static class SplitStageInfo { |
| public int taskId = -1; |
| @StagePosition |
| public int stagePosition = STAGE_POSITION_UNDEFINED; |
| @StageType |
| public int stageType = STAGE_TYPE_UNDEFINED; |
| } |
| |
| public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) { |
| return position == STAGE_POSITION_TOP_OR_LEFT |
| ? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP |
| : LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM; |
| } |
| |
| public static @StagePosition int getOppositeStagePosition(@StagePosition int position) { |
| if (position == STAGE_POSITION_UNDEFINED) { |
| return position; |
| } |
| return position == STAGE_POSITION_TOP_OR_LEFT ? STAGE_POSITION_BOTTOM_OR_RIGHT |
| : STAGE_POSITION_TOP_OR_LEFT; |
| } |
| |
| public static class SplitSelectSource { |
| |
| /** Keep in sync w/ ActivityTaskManager#INVALID_TASK_ID (unreference-able) */ |
| private static final int INVALID_TASK_ID = -1; |
| |
| private View view; |
| private Drawable drawable; |
| public final Intent intent; |
| public final SplitPositionOption position; |
| public final ItemInfo itemInfo; |
| public final StatsLogManager.EventEnum splitEvent; |
| /** Represents the taskId of the first app to start in split screen */ |
| public int alreadyRunningTaskId = INVALID_TASK_ID; |
| /** |
| * If {@code true}, animates the view represented by {@link #alreadyRunningTaskId} into the |
| * split placeholder view |
| */ |
| public boolean animateCurrentTaskDismissal; |
| |
| public SplitSelectSource(View view, Drawable drawable, Intent intent, |
| SplitPositionOption position, ItemInfo itemInfo, |
| StatsLogManager.EventEnum splitEvent) { |
| this.view = view; |
| this.drawable = drawable; |
| this.intent = intent; |
| this.position = position; |
| this.itemInfo = itemInfo; |
| this.splitEvent = splitEvent; |
| } |
| |
| public Drawable getDrawable() { |
| return drawable; |
| } |
| |
| public View getView() { |
| return view; |
| } |
| } |
| } |