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