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 public static class SplitPositionOption { 81 public final int iconResId; 82 public final int textResId; 83 @StagePosition 84 public final int stagePosition; 85 86 @StageType 87 public final int mStageType; 88 SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType)89 public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) { 90 this.iconResId = iconResId; 91 this.textResId = textResId; 92 this.stagePosition = stagePosition; 93 mStageType = stageType; 94 } 95 } 96 97 /** 98 * NOTE: Engineers complained about too little ambiguity in the last survey, so there is a class 99 * with the same name/functionality in wm.shell.util (which launcher3 cannot be built against) 100 * 101 * If you make changes here, consider making the same changes there 102 * TODO(b/254378592): We really need to consolidate this 103 */ 104 public static class SplitBounds { 105 public final Rect leftTopBounds; 106 public final Rect rightBottomBounds; 107 /** This rect represents the actual gap between the two apps */ 108 public final Rect visualDividerBounds; 109 // This class is orientation-agnostic, so we compute both for later use 110 public final float topTaskPercent; 111 public final float leftTaskPercent; 112 public final float dividerWidthPercent; 113 public final float dividerHeightPercent; 114 public final int snapPosition; 115 116 /** 117 * If {@code true}, that means at the time of creation of this object, the 118 * split-screened apps were vertically stacked. This is useful in scenarios like 119 * rotation where the bounds won't change, but this variable can indicate what orientation 120 * the bounds were originally in 121 */ 122 public final boolean appsStackedVertically; 123 /** 124 * If {@code true}, that means at the time of creation of this object, the phone was in 125 * seascape orientation. This is important on devices with insets, because they do not split 126 * evenly -- one of the insets must be slightly larger to account for the inset. 127 * From landscape, it is the leftTop task that expands slightly. 128 * From seascape, it is the rightBottom task that expands slightly. 129 */ 130 public final boolean initiatedFromSeascape; 131 public final int leftTopTaskId; 132 public final int rightBottomTaskId; 133 SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, int rightBottomTaskId, int snapPosition)134 public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, 135 int rightBottomTaskId, int snapPosition) { 136 this.leftTopBounds = leftTopBounds; 137 this.rightBottomBounds = rightBottomBounds; 138 this.leftTopTaskId = leftTopTaskId; 139 this.rightBottomTaskId = rightBottomTaskId; 140 this.snapPosition = snapPosition; 141 142 if (rightBottomBounds.top > leftTopBounds.top) { 143 // vertical apps, horizontal divider 144 this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, 145 leftTopBounds.right, rightBottomBounds.top); 146 appsStackedVertically = true; 147 initiatedFromSeascape = false; 148 } else { 149 // horizontal apps, vertical divider 150 this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, 151 rightBottomBounds.left, leftTopBounds.bottom); 152 appsStackedVertically = false; 153 // The following check is unreliable on devices without insets 154 // (initiatedFromSeascape will always be set to false.) This happens to be OK for 155 // all our current uses, but should be refactored. 156 // TODO: Create a more reliable check, or refactor how splitting works on devices 157 // with insets. 158 if (rightBottomBounds.width() > leftTopBounds.width()) { 159 initiatedFromSeascape = true; 160 } else { 161 initiatedFromSeascape = false; 162 } 163 } 164 165 float totalWidth = rightBottomBounds.right - leftTopBounds.left; 166 float totalHeight = rightBottomBounds.bottom - leftTopBounds.top; 167 leftTaskPercent = leftTopBounds.width() / totalWidth; 168 topTaskPercent = leftTopBounds.height() / totalHeight; 169 dividerWidthPercent = visualDividerBounds.width() / totalWidth; 170 dividerHeightPercent = visualDividerBounds.height() / totalHeight; 171 } 172 173 @Override toString()174 public String toString() { 175 return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" 176 + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" 177 + "Divider: " + visualDividerBounds + "\n" 178 + "AppsVertical? " + appsStackedVertically + "\n" 179 + "snapPosition: " + snapPosition; 180 } 181 } 182 183 public static class SplitStageInfo { 184 public int taskId = -1; 185 @StagePosition 186 public int stagePosition = STAGE_POSITION_UNDEFINED; 187 @StageType 188 public int stageType = STAGE_TYPE_UNDEFINED; 189 190 @Override toString()191 public String toString() { 192 return "SplitStageInfo { taskId=" + taskId 193 + ", stagePosition=" + stagePosition + ", stageType=" + stageType + " }"; 194 } 195 } 196 getLogEventForPosition(@tagePosition int position)197 public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) { 198 return position == STAGE_POSITION_TOP_OR_LEFT 199 ? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP 200 : LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM; 201 } 202 getOppositeStagePosition(@tagePosition int position)203 public static @StagePosition int getOppositeStagePosition(@StagePosition int position) { 204 if (position == STAGE_POSITION_UNDEFINED) { 205 return position; 206 } 207 return position == STAGE_POSITION_TOP_OR_LEFT ? STAGE_POSITION_BOTTOM_OR_RIGHT 208 : STAGE_POSITION_TOP_OR_LEFT; 209 } 210 211 public static class SplitSelectSource { 212 213 /** Keep in sync w/ ActivityTaskManager#INVALID_TASK_ID (unreference-able) */ 214 private static final int INVALID_TASK_ID = -1; 215 216 private View view; 217 private Drawable drawable; 218 public final Intent intent; 219 public final SplitPositionOption position; 220 public final ItemInfo itemInfo; 221 public final StatsLogManager.EventEnum splitEvent; 222 /** Represents the taskId of the first app to start in split screen */ 223 public int alreadyRunningTaskId = INVALID_TASK_ID; 224 /** 225 * If {@code true}, animates the view represented by {@link #alreadyRunningTaskId} into the 226 * split placeholder view 227 */ 228 public boolean animateCurrentTaskDismissal; 229 SplitSelectSource(View view, Drawable drawable, Intent intent, SplitPositionOption position, ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)230 public SplitSelectSource(View view, Drawable drawable, Intent intent, 231 SplitPositionOption position, ItemInfo itemInfo, 232 StatsLogManager.EventEnum splitEvent) { 233 this.view = view; 234 this.drawable = drawable; 235 this.intent = intent; 236 this.position = position; 237 this.itemInfo = itemInfo; 238 this.splitEvent = splitEvent; 239 } 240 getDrawable()241 public Drawable getDrawable() { 242 return drawable; 243 } 244 getView()245 public View getView() { 246 return view; 247 } 248 } 249 } 250