1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.util; 18 19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP; 20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM; 21 22 import static java.lang.annotation.RetentionPolicy.SOURCE; 23 24 import android.content.Intent; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.view.View; 28 29 import androidx.annotation.IntDef; 30 31 import com.android.launcher3.logging.StatsLogManager; 32 import com.android.launcher3.model.data.ItemInfo; 33 34 import java.lang.annotation.Retention; 35 36 public final class SplitConfigurationOptions { 37 38 /////////////////////////////////// 39 // Taken from 40 // frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java 41 /** 42 * Stage position isn't specified normally meaning to use what ever it is currently set to. 43 */ 44 public static final int STAGE_POSITION_UNDEFINED = -1; 45 /** 46 * Specifies that a stage is positioned at the top half of the screen if 47 * in portrait mode or at the left half of the screen if in landscape mode. 48 */ 49 public static final int STAGE_POSITION_TOP_OR_LEFT = 0; 50 51 /** 52 * Specifies that a stage is positioned at the bottom half of the screen if 53 * in portrait mode or at the right half of the screen if in landscape mode. 54 */ 55 public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1; 56 57 @Retention(SOURCE) 58 @IntDef({STAGE_POSITION_UNDEFINED, STAGE_POSITION_TOP_OR_LEFT, STAGE_POSITION_BOTTOM_OR_RIGHT}) 59 public @interface StagePosition {} 60 61 /** 62 * Stage type isn't specified normally meaning to use what ever the default is. 63 * E.g. exit split-screen and launch the app in fullscreen. 64 */ 65 public static final int STAGE_TYPE_UNDEFINED = -1; 66 /** 67 * The main stage type. 68 */ 69 public static final int STAGE_TYPE_MAIN = 0; 70 71 /** 72 * The side stage type. 73 */ 74 public static final int STAGE_TYPE_SIDE = 1; 75 76 @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE}) 77 public @interface StageType {} 78 /////////////////////////////////// 79 80 /** 81 * Default split ratio for launching app pair from overview. 82 */ 83 public static final float DEFAULT_SPLIT_RATIO = 0.5f; 84 85 public static class SplitPositionOption { 86 public final int iconResId; 87 public final int textResId; 88 @StagePosition 89 public final int stagePosition; 90 91 @StageType 92 public final int mStageType; 93 SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType)94 public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) { 95 this.iconResId = iconResId; 96 this.textResId = textResId; 97 this.stagePosition = stagePosition; 98 mStageType = stageType; 99 } 100 } 101 102 /** 103 * NOTE: Engineers complained about too little ambiguity in the last survey, so there is a class 104 * with the same name/functionality in wm.shell.util (which launcher3 cannot be built against) 105 * 106 * If you make changes here, consider making the same changes there 107 * TODO(b/254378592): We really need to consolidate this 108 */ 109 public static class SplitBounds { 110 public final Rect leftTopBounds; 111 public final Rect rightBottomBounds; 112 /** This rect represents the actual gap between the two apps */ 113 public final Rect visualDividerBounds; 114 // This class is orientation-agnostic, so we compute both for later use 115 public final float topTaskPercent; 116 public final float leftTaskPercent; 117 public final float dividerWidthPercent; 118 public final float dividerHeightPercent; 119 /** 120 * If {@code true}, that means at the time of creation of this object, the 121 * split-screened apps were vertically stacked. This is useful in scenarios like 122 * rotation where the bounds won't change, but this variable can indicate what orientation 123 * the bounds were originally in 124 */ 125 public final boolean appsStackedVertically; 126 /** 127 * If {@code true}, that means at the time of creation of this object, the phone was in 128 * seascape orientation. This is important on devices with insets, because they do not split 129 * evenly -- one of the insets must be slightly larger to account for the inset. 130 * From landscape, it is the leftTop task that expands slightly. 131 * From seascape, it is the rightBottom task that expands slightly. 132 */ 133 public final boolean initiatedFromSeascape; 134 public final int leftTopTaskId; 135 public final int rightBottomTaskId; 136 SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, int rightBottomTaskId)137 public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, 138 int rightBottomTaskId) { 139 this.leftTopBounds = leftTopBounds; 140 this.rightBottomBounds = rightBottomBounds; 141 this.leftTopTaskId = leftTopTaskId; 142 this.rightBottomTaskId = rightBottomTaskId; 143 144 if (rightBottomBounds.top > leftTopBounds.top) { 145 // vertical apps, horizontal divider 146 this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, 147 leftTopBounds.right, rightBottomBounds.top); 148 appsStackedVertically = true; 149 initiatedFromSeascape = false; 150 } else { 151 // horizontal apps, vertical divider 152 this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, 153 rightBottomBounds.left, leftTopBounds.bottom); 154 appsStackedVertically = false; 155 // The following check is unreliable on devices without insets 156 // (initiatedFromSeascape will always be set to false.) This happens to be OK for 157 // all our current uses, but should be refactored. 158 // TODO: Create a more reliable check, or refactor how splitting works on devices 159 // with insets. 160 if (rightBottomBounds.width() > leftTopBounds.width()) { 161 initiatedFromSeascape = true; 162 } else { 163 initiatedFromSeascape = false; 164 } 165 } 166 167 float totalWidth = rightBottomBounds.right - leftTopBounds.left; 168 float totalHeight = rightBottomBounds.bottom - leftTopBounds.top; 169 leftTaskPercent = leftTopBounds.width() / totalWidth; 170 topTaskPercent = leftTopBounds.height() / totalHeight; 171 dividerWidthPercent = visualDividerBounds.width() / totalWidth; 172 dividerHeightPercent = visualDividerBounds.height() / totalHeight; 173 } 174 } 175 176 public static class SplitStageInfo { 177 public int taskId = -1; 178 @StagePosition 179 public int stagePosition = STAGE_POSITION_UNDEFINED; 180 @StageType 181 public int stageType = STAGE_TYPE_UNDEFINED; 182 } 183 getLogEventForPosition(@tagePosition int position)184 public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) { 185 return position == STAGE_POSITION_TOP_OR_LEFT 186 ? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP 187 : LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM; 188 } 189 getOppositeStagePosition(@tagePosition int position)190 public static @StagePosition int getOppositeStagePosition(@StagePosition int position) { 191 if (position == STAGE_POSITION_UNDEFINED) { 192 return position; 193 } 194 return position == STAGE_POSITION_TOP_OR_LEFT ? STAGE_POSITION_BOTTOM_OR_RIGHT 195 : STAGE_POSITION_TOP_OR_LEFT; 196 } 197 198 public static class SplitSelectSource { 199 200 /** Keep in sync w/ ActivityTaskManager#INVALID_TASK_ID (unreference-able) */ 201 private static final int INVALID_TASK_ID = -1; 202 203 private View view; 204 private Drawable drawable; 205 public final Intent intent; 206 public final SplitPositionOption position; 207 public final ItemInfo itemInfo; 208 public final StatsLogManager.EventEnum splitEvent; 209 /** Represents the taskId of the first app to start in split screen */ 210 public int alreadyRunningTaskId = INVALID_TASK_ID; 211 /** 212 * If {@code true}, animates the view represented by {@link #alreadyRunningTaskId} into the 213 * split placeholder view 214 */ 215 public boolean animateCurrentTaskDismissal; 216 SplitSelectSource(View view, Drawable drawable, Intent intent, SplitPositionOption position, ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)217 public SplitSelectSource(View view, Drawable drawable, Intent intent, 218 SplitPositionOption position, ItemInfo itemInfo, 219 StatsLogManager.EventEnum splitEvent) { 220 this.view = view; 221 this.drawable = drawable; 222 this.intent = intent; 223 this.position = position; 224 this.itemInfo = itemInfo; 225 this.splitEvent = splitEvent; 226 } 227 getDrawable()228 public Drawable getDrawable() { 229 return drawable; 230 } 231 getView()232 public View getView() { 233 return view; 234 } 235 } 236 } 237