• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.touch;
18 
19 import static android.view.Gravity.BOTTOM;
20 import static android.view.Gravity.CENTER_HORIZONTAL;
21 import static android.view.Gravity.END;
22 import static android.view.Gravity.START;
23 import static android.view.Gravity.TOP;
24 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
25 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
26 
27 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
28 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
29 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
30 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
31 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
32 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
33 
34 import android.content.res.Resources;
35 import android.graphics.Matrix;
36 import android.graphics.PointF;
37 import android.graphics.Rect;
38 import android.graphics.RectF;
39 import android.graphics.drawable.ShapeDrawable;
40 import android.util.FloatProperty;
41 import android.util.Pair;
42 import android.view.MotionEvent;
43 import android.view.Surface;
44 import android.view.VelocityTracker;
45 import android.view.View;
46 import android.view.accessibility.AccessibilityEvent;
47 import android.widget.FrameLayout;
48 import android.widget.LinearLayout;
49 
50 import com.android.launcher3.DeviceProfile;
51 import com.android.launcher3.R;
52 import com.android.launcher3.Utilities;
53 import com.android.launcher3.util.SplitConfigurationOptions;
54 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
55 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
56 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 
61 public class PortraitPagedViewHandler implements PagedOrientationHandler {
62 
63     private final Matrix mTmpMatrix = new Matrix();
64     private final RectF mTmpRectF = new RectF();
65 
66     @Override
getPrimaryValue(T x, T y)67     public <T> T getPrimaryValue(T x, T y) {
68         return x;
69     }
70 
71     @Override
getSecondaryValue(T x, T y)72     public <T> T getSecondaryValue(T x, T y) {
73         return y;
74     }
75 
76     @Override
getPrimaryValue(int x, int y)77     public int getPrimaryValue(int x, int y) {
78         return x;
79     }
80 
81     @Override
getSecondaryValue(int x, int y)82     public int getSecondaryValue(int x, int y) {
83         return y;
84     }
85 
86     @Override
getPrimaryValue(float x, float y)87     public float getPrimaryValue(float x, float y) {
88         return x;
89     }
90 
91     @Override
getSecondaryValue(float x, float y)92     public float getSecondaryValue(float x, float y) {
93         return y;
94     }
95 
96     @Override
isLayoutNaturalToLauncher()97     public boolean isLayoutNaturalToLauncher() {
98         return true;
99     }
100 
101     @Override
adjustFloatingIconStartVelocity(PointF velocity)102     public void adjustFloatingIconStartVelocity(PointF velocity) {
103         //no-op
104     }
105 
106     @Override
fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile)107     public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
108         if (outStartRect.left > deviceProfile.widthPx) {
109             outStartRect.offsetTo(0, outStartRect.top);
110         } else if (outStartRect.left < -deviceProfile.widthPx) {
111             outStartRect.offsetTo(0, outStartRect.top);
112         }
113     }
114 
115     @Override
setPrimary(T target, Int2DAction<T> action, int param)116     public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
117         action.call(target, param, 0);
118     }
119 
120     @Override
setPrimary(T target, Float2DAction<T> action, float param)121     public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
122         action.call(target, param, 0);
123     }
124 
125     @Override
setSecondary(T target, Float2DAction<T> action, float param)126     public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
127         action.call(target, 0, param);
128     }
129 
130     @Override
set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam)131     public <T> void set(T target, Int2DAction<T> action, int primaryParam,
132             int secondaryParam) {
133         action.call(target, primaryParam, secondaryParam);
134     }
135 
136     @Override
getPrimaryDirection(MotionEvent event, int pointerIndex)137     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
138         return event.getX(pointerIndex);
139     }
140 
141     @Override
getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)142     public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
143         return velocityTracker.getXVelocity(pointerId);
144     }
145 
146     @Override
getMeasuredSize(View view)147     public int getMeasuredSize(View view) {
148         return view.getMeasuredWidth();
149     }
150 
151     @Override
getPrimarySize(View view)152     public int getPrimarySize(View view) {
153         return view.getWidth();
154     }
155 
156     @Override
getPrimarySize(RectF rect)157     public float getPrimarySize(RectF rect) {
158         return rect.width();
159     }
160 
161     @Override
getStart(RectF rect)162     public float getStart(RectF rect) {
163         return rect.left;
164     }
165 
166     @Override
getEnd(RectF rect)167     public float getEnd(RectF rect) {
168         return rect.right;
169     }
170 
171     @Override
getClearAllSidePadding(View view, boolean isRtl)172     public int getClearAllSidePadding(View view, boolean isRtl) {
173         return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
174     }
175 
176     @Override
getSecondaryDimension(View view)177     public int getSecondaryDimension(View view) {
178         return view.getHeight();
179     }
180 
181     @Override
getPrimaryViewTranslate()182     public FloatProperty<View> getPrimaryViewTranslate() {
183         return VIEW_TRANSLATE_X;
184     }
185 
186     @Override
getSecondaryViewTranslate()187     public FloatProperty<View> getSecondaryViewTranslate() {
188         return VIEW_TRANSLATE_Y;
189     }
190 
191     @Override
getPrimaryScroll(View view)192     public int getPrimaryScroll(View view) {
193         return view.getScrollX();
194     }
195 
196     @Override
getPrimaryScale(View view)197     public float getPrimaryScale(View view) {
198         return view.getScaleX();
199     }
200 
201     @Override
setMaxScroll(AccessibilityEvent event, int maxScroll)202     public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
203         event.setMaxScrollX(maxScroll);
204     }
205 
206     @Override
getRecentsRtlSetting(Resources resources)207     public boolean getRecentsRtlSetting(Resources resources) {
208         return !Utilities.isRtl(resources);
209     }
210 
211     @Override
getDegreesRotated()212     public float getDegreesRotated() {
213         return 0;
214     }
215 
216     @Override
getRotation()217     public int getRotation() {
218         return Surface.ROTATION_0;
219     }
220 
221     @Override
setPrimaryScale(View view, float scale)222     public void setPrimaryScale(View view, float scale) {
223         view.setScaleX(scale);
224     }
225 
226     @Override
setSecondaryScale(View view, float scale)227     public void setSecondaryScale(View view, float scale) {
228         view.setScaleY(scale);
229     }
230 
231     @Override
getChildStart(View view)232     public int getChildStart(View view) {
233         return view.getLeft();
234     }
235 
236     @Override
getCenterForPage(View view, Rect insets)237     public int getCenterForPage(View view, Rect insets) {
238         return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
239             - insets.bottom - view.getPaddingBottom()) / 2;
240     }
241 
242     @Override
getScrollOffsetStart(View view, Rect insets)243     public int getScrollOffsetStart(View view, Rect insets) {
244         return insets.left + view.getPaddingLeft();
245     }
246 
247     @Override
getScrollOffsetEnd(View view, Rect insets)248     public int getScrollOffsetEnd(View view, Rect insets) {
249         return view.getWidth() - view.getPaddingRight() - insets.right;
250     }
251 
getSecondaryTranslationDirectionFactor()252     public int getSecondaryTranslationDirectionFactor() {
253         return -1;
254     }
255 
256     @Override
getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)257     public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
258         if (deviceProfile.isLandscape && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
259             return -1;
260         } else {
261             return 1;
262         }
263     }
264 
265     @Override
getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile, float taskInsetMargin)266     public float getTaskMenuX(float x, View thumbnailView,
267             DeviceProfile deviceProfile, float taskInsetMargin) {
268         if (deviceProfile.isLandscape) {
269             return x + taskInsetMargin
270                     + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
271         } else {
272             return x + taskInsetMargin;
273         }
274     }
275 
276     @Override
getTaskMenuY(float y, View thumbnailView, int stagePosition, View taskMenuView, float taskInsetMargin)277     public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
278             View taskMenuView, float taskInsetMargin) {
279         return y + taskInsetMargin;
280     }
281 
282     @Override
getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile, @StagePosition int stagePosition)283     public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
284             @StagePosition int stagePosition) {
285         return deviceProfile.isLandscape && !deviceProfile.isTablet
286                 ? thumbnailView.getMeasuredHeight()
287                 : thumbnailView.getMeasuredWidth();
288     }
289 
290     @Override
setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable)291     public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
292             LinearLayout taskMenuLayout, int dividerSpacing,
293             ShapeDrawable dividerDrawable) {
294         taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
295         dividerDrawable.setIntrinsicHeight(dividerSpacing);
296         taskMenuLayout.setDividerDrawable(dividerDrawable);
297     }
298 
299     @Override
setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile)300     public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
301             LinearLayout viewGroup, DeviceProfile deviceProfile) {
302         viewGroup.setOrientation(LinearLayout.HORIZONTAL);
303         lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
304         lp.height = WRAP_CONTENT;
305     }
306 
307     @Override
getDwbLayoutTranslations(int taskViewWidth, int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)308     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
309             int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
310             View[] thumbnailViews, int desiredTaskId, View banner) {
311         float translationX = 0;
312         float translationY = 0;
313         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
314         banner.setPivotX(0);
315         banner.setPivotY(0);
316         banner.setRotation(getDegreesRotated());
317         if (splitBounds == null) {
318             // Single, fullscreen case
319             bannerParams.width = MATCH_PARENT;
320             bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
321             return new Pair<>(translationX, translationY);
322         }
323 
324         bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL);
325 
326         // Set correct width
327         if (desiredTaskId == splitBounds.leftTopTaskId) {
328             bannerParams.width = thumbnailViews[0].getMeasuredWidth();
329         } else {
330             bannerParams.width = thumbnailViews[1].getMeasuredWidth();
331         }
332 
333         // Set translations
334         if (deviceProfile.isLandscape) {
335             if (desiredTaskId == splitBounds.rightBottomTaskId) {
336                 float leftTopTaskPercent = splitBounds.appsStackedVertically
337                         ? splitBounds.topTaskPercent
338                         : splitBounds.leftTaskPercent;
339                 float dividerThicknessPercent = splitBounds.appsStackedVertically
340                         ? splitBounds.dividerHeightPercent
341                         : splitBounds.dividerWidthPercent;
342                 translationX = ((taskViewWidth * leftTopTaskPercent)
343                         + (taskViewWidth * dividerThicknessPercent));
344             }
345         } else {
346             if (desiredTaskId == splitBounds.leftTopTaskId) {
347                 FrameLayout.LayoutParams snapshotParams =
348                         (FrameLayout.LayoutParams) thumbnailViews[0]
349                                 .getLayoutParams();
350                 float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
351                         ? (1f - splitBounds.topTaskPercent)
352                         : (1f - splitBounds.leftTaskPercent);
353                 translationY = -((taskViewHeight - snapshotParams.topMargin)
354                         * bottomRightTaskPlusDividerPercent);
355             }
356         }
357         return new Pair<>(translationX, translationY);
358     }
359 
360     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
361 
362     @Override
getUpDownSwipeDirection()363     public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
364         return VERTICAL;
365     }
366 
367     @Override
getUpDirection(boolean isRtl)368     public int getUpDirection(boolean isRtl) {
369         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
370         return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
371     }
372 
373     @Override
isGoingUp(float displacement, boolean isRtl)374     public boolean isGoingUp(float displacement, boolean isRtl) {
375         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
376         return displacement < 0;
377     }
378 
379     @Override
getTaskDragDisplacementFactor(boolean isRtl)380     public int getTaskDragDisplacementFactor(boolean isRtl) {
381         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
382         return 1;
383     }
384 
385     /* -------------------- */
386 
387     @Override
getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)388     public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
389         boolean layoutChild) {
390         final int childWidth = child.getMeasuredWidth();
391         final int childRight = childStart + childWidth;
392         final int childHeight = child.getMeasuredHeight();
393         final int childTop = pageCenter - childHeight / 2;
394         if (layoutChild) {
395             child.layout(childStart, childTop, childRight, childTop + childHeight);
396         }
397         return new ChildBounds(childWidth, childHeight, childRight, childTop);
398     }
399 
400     @Override
getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)401     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
402         return dp.heightPx - rect.bottom;
403     }
404 
405     @Override
getSplitPositionOptions(DeviceProfile dp)406     public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
407         if (dp.isTablet) {
408             return Utilities.getSplitPositionOptions(dp);
409         }
410 
411         List<SplitPositionOption> options = new ArrayList<>();
412         if (dp.isSeascape()) {
413             options.add(new SplitPositionOption(
414                     R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
415                     STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
416         } else if (dp.isLandscape) {
417             options.add(new SplitPositionOption(
418                     R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
419                     STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
420         } else {
421             // Only add top option
422             options.add(new SplitPositionOption(
423                     R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
424                     STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
425         }
426         return options;
427     }
428 
429     @Override
getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset, DeviceProfile dp, @StagePosition int stagePosition, Rect out)430     public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
431             DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
432         int screenWidth = dp.widthPx;
433         int screenHeight = dp.heightPx;
434         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
435         int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
436 
437         out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
438         if (!dp.isLandscape) {
439             // portrait, phone or tablet - spans width of screen, nothing else to do
440             out.inset(placeholderInset, 0);
441 
442             // Adjust the top to account for content off screen. This will help to animate the view
443             // in with rounded corners.
444             int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
445                     / screenWidth);
446             out.top -= (totalHeight - placeholderHeight);
447             return;
448         }
449 
450         // Now we rotate the portrait rect depending on what side we want pinned
451 
452         float postRotateScale = (float) screenHeight / screenWidth;
453         mTmpMatrix.reset();
454         mTmpMatrix.postRotate(pinToRight ? 90 : 270);
455         mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
456         // The placeholder height stays constant after rotation, so we don't change width scale
457         mTmpMatrix.postScale(1, postRotateScale);
458 
459         mTmpRectF.set(out);
460         mTmpMatrix.mapRect(mTmpRectF);
461         mTmpRectF.inset(0, placeholderInset);
462         mTmpRectF.roundOut(out);
463 
464         // Adjust the top to account for content off screen. This will help to animate the view in
465         // with rounded corners.
466         int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
467                 / screenHeight);
468         int width = out.width();
469         if (pinToRight) {
470             out.right += totalWidth - width;
471         } else {
472             out.left -= totalWidth - width;
473         }
474     }
475 
476     @Override
updateSplitIconParams(View out, float onScreenRectCenterX, float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY, int drawableWidth, int drawableHeight, DeviceProfile dp, @StagePosition int stagePosition)477     public void updateSplitIconParams(View out, float onScreenRectCenterX,
478             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
479             int drawableWidth, int drawableHeight, DeviceProfile dp,
480             @StagePosition int stagePosition) {
481         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
482         float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
483         if (!dp.isLandscape) {
484             out.setX(onScreenRectCenterX / fullscreenScaleX
485                     - 1.0f * drawableWidth / 2);
486             out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
487                     - 1.0f * drawableHeight / 2);
488         } else {
489             if (pinToRight) {
490                 out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
491                         - 1.0f * drawableWidth / 2);
492             } else {
493                 out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
494                         - 1.0f * drawableWidth / 2);
495             }
496             out.setY(onScreenRectCenterY / fullscreenScaleY
497                     - 1.0f * drawableHeight / 2);
498         }
499     }
500 
501     /**
502      * The split placeholder comes with a default inset to buffer the icon from the top of the
503      * screen. But if the device already has a large inset (from cutouts etc), use that instead.
504      */
getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight)505     private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) {
506         int insetThickness;
507         if (!dp.isLandscape) {
508             insetThickness = dp.getInsets().top;
509         } else {
510             insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
511         }
512         return Math.max(insetThickness - dp.splitPlaceholderInset, 0);
513     }
514 
515     @Override
setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, int splitInstructionsWidth)516     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
517             int splitInstructionsWidth) {
518         out.setPivotX(0);
519         out.setPivotY(splitInstructionsHeight);
520         out.setRotation(getDegreesRotated());
521         int distanceToEdge;
522         if (dp.isPhone) {
523             if (dp.isLandscape) {
524                 distanceToEdge = out.getResources().getDimensionPixelSize(
525                         R.dimen.split_instructions_bottom_margin_phone_landscape);
526             } else {
527                 distanceToEdge = out.getResources().getDimensionPixelSize(
528                         R.dimen.split_instructions_bottom_margin_phone_portrait);
529             }
530         } else {
531             distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
532         }
533 
534         // Center the view in case of unbalanced insets on left or right of screen
535         int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
536         // Adjust for any insets on the bottom edge
537         int insetCorrectionY = dp.getInsets().bottom;
538         out.setTranslationX(insetCorrectionX);
539         out.setTranslationY(-distanceToEdge + insetCorrectionY);
540         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
541         lp.gravity = CENTER_HORIZONTAL | BOTTOM;
542         out.setLayoutParams(lp);
543     }
544 
545     @Override
getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2)546     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
547             @StagePosition int stagePosition, Rect out1, Rect out2) {
548         int screenHeight = dp.heightPx;
549         int screenWidth = dp.widthPx;
550         out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
551         out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
552         if (!dp.isLandscape) {
553             // Portrait - the window bounds are always top and bottom half
554             return;
555         }
556 
557         // Now we rotate the portrait rect depending on what side we want pinned
558         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
559         float postRotateScale = (float) screenHeight / screenWidth;
560 
561         mTmpMatrix.reset();
562         mTmpMatrix.postRotate(pinToRight ? 90 : 270);
563         mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
564         mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
565 
566         mTmpRectF.set(out1);
567         mTmpMatrix.mapRect(mTmpRectF);
568         mTmpRectF.roundOut(out1);
569 
570         mTmpRectF.set(out2);
571         mTmpMatrix.mapRect(mTmpRectF);
572         mTmpRectF.roundOut(out2);
573     }
574 
575     @Override
setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo, int desiredStagePosition)576     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
577             SplitBounds splitInfo, int desiredStagePosition) {
578         float topLeftTaskPercent = splitInfo.appsStackedVertically
579                 ? splitInfo.topTaskPercent
580                 : splitInfo.leftTaskPercent;
581         float dividerBarPercent = splitInfo.appsStackedVertically
582                 ? splitInfo.dividerHeightPercent
583                 : splitInfo.dividerWidthPercent;
584 
585         float scale = (float) outRect.height() / dp.availableHeightPx;
586         float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
587         float scaledTopTaskHeight = topTaskHeight * scale;
588         float dividerHeight = dp.availableHeightPx * dividerBarPercent;
589         float scaledDividerHeight = dividerHeight * scale;
590 
591         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
592             if (splitInfo.appsStackedVertically) {
593                 outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
594             } else {
595                 outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
596             }
597         } else {
598             if (splitInfo.appsStackedVertically) {
599                 outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
600             } else {
601                 outRect.left += Math.round(outRect.width()
602                         * (topLeftTaskPercent + dividerBarPercent));
603             }
604         }
605     }
606 
607     @Override
measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl)608     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
609             int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
610             DeviceProfile dp, boolean isRtl) {
611         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
612         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
613         int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically
614                 ? splitBoundsConfig.dividerHeightPercent * dp.availableHeightPx
615                 : splitBoundsConfig.dividerWidthPercent * parentWidth);
616         int primarySnapshotHeight;
617         int primarySnapshotWidth;
618         int secondarySnapshotHeight;
619         int secondarySnapshotWidth;
620         float taskPercent = splitBoundsConfig.appsStackedVertically ?
621                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
622         if (dp.isLandscape) {
623             primarySnapshotHeight = totalThumbnailHeight;
624             primarySnapshotWidth = Math.round(parentWidth * taskPercent);
625 
626             secondarySnapshotHeight = totalThumbnailHeight;
627             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
628             int translationX = primarySnapshotWidth + dividerBar;
629             if (isRtl) {
630                 primarySnapshot.setTranslationX(-translationX);
631                 secondarySnapshot.setTranslationX(0);
632             } else {
633                 secondarySnapshot.setTranslationX(translationX);
634                 primarySnapshot.setTranslationX(0);
635             }
636             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
637 
638             // Reset unused translations
639             primarySnapshot.setTranslationY(0);
640         } else {
641             float scale = (float) totalThumbnailHeight / dp.availableHeightPx;
642             float topTaskHeight = dp.availableHeightPx * taskPercent;
643             float finalDividerHeight = dividerBar * scale;
644             float scaledTopTaskHeight = topTaskHeight * scale;
645             primarySnapshotWidth = parentWidth;
646             primarySnapshotHeight = Math.round(scaledTopTaskHeight);
647 
648             secondarySnapshotWidth = parentWidth;
649             secondarySnapshotHeight = Math.round(totalThumbnailHeight - primarySnapshotHeight
650                     - finalDividerHeight);
651             float translationY = primarySnapshotHeight + spaceAboveSnapshot + finalDividerHeight;
652             secondarySnapshot.setTranslationY(translationY);
653 
654             FrameLayout.LayoutParams primaryParams =
655                     (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
656             FrameLayout.LayoutParams secondaryParams =
657                     (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
658             secondaryParams.topMargin = 0;
659             primaryParams.topMargin = spaceAboveSnapshot;
660 
661             // Reset unused translations
662             primarySnapshot.setTranslationY(0);
663             secondarySnapshot.setTranslationX(0);
664             primarySnapshot.setTranslationX(0);
665         }
666         primarySnapshot.measure(
667                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
668                 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
669         secondarySnapshot.measure(
670                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
671                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
672                         View.MeasureSpec.EXACTLY));
673         primarySnapshot.setScaleX(1);
674         secondarySnapshot.setScaleX(1);
675         primarySnapshot.setScaleY(1);
676         secondarySnapshot.setScaleY(1);
677     }
678 
679     @Override
setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl)680     public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
681             int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
682         iconParams.gravity = TOP | CENTER_HORIZONTAL;
683         // Reset margins, since they may have been set on rotation
684         iconParams.leftMargin = iconParams.rightMargin = 0;
685         iconParams.topMargin = iconParams.bottomMargin = 0;
686     }
687 
688     @Override
setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, DeviceProfile deviceProfile, SplitBounds splitConfig)689     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
690             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
691             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
692             DeviceProfile deviceProfile, SplitBounds splitConfig) {
693         FrameLayout.LayoutParams primaryIconParams =
694                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
695         FrameLayout.LayoutParams secondaryIconParams =
696                 new FrameLayout.LayoutParams(primaryIconParams);
697 
698         if (deviceProfile.isLandscape) {
699             // We calculate the "midpoint" of the thumbnail area, and place the icons there.
700             // This is the place where the thumbnail area splits by default, in a near-50/50 split.
701             // It is usually not exactly 50/50, due to insets/screen cutouts.
702             int fullscreenInsetThickness = deviceProfile.isSeascape()
703                     ? deviceProfile.getInsets().right
704                     : deviceProfile.getInsets().left;
705             int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
706                     - fullscreenInsetThickness) / 2);
707             float midpointFromEndPct = (float) fullscreenMidpointFromBottom
708                     / deviceProfile.widthPx;
709             float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
710             int spaceAboveSnapshots = 0;
711             int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
712             int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
713                     * midpointFromEndPct);
714             int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
715 
716             if (deviceProfile.isSeascape()) {
717                 primaryIconParams.gravity = TOP | (isRtl ? END : START);
718                 secondaryIconParams.gravity = TOP | (isRtl ? END : START);
719                 if (splitConfig.initiatedFromSeascape) {
720                     // if the split was initiated from seascape,
721                     // the task on the right (secondary) is slightly larger
722                     primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
723                     secondaryIconView.setTranslationX(bottomToMidpointOffset);
724                 } else {
725                     // if not,
726                     // the task on the left (primary) is slightly larger
727                     primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
728                             - taskIconHeight);
729                     secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
730                 }
731             } else {
732                 primaryIconParams.gravity = TOP | (isRtl ? START : END);
733                 secondaryIconParams.gravity = TOP | (isRtl ? START : END);
734                 if (!splitConfig.initiatedFromSeascape) {
735                     // if the split was initiated from landscape,
736                     // the task on the left (primary) is slightly larger
737                     primaryIconView.setTranslationX(-bottomToMidpointOffset);
738                     secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
739                 } else {
740                     // if not,
741                     // the task on the right (secondary) is slightly larger
742                     primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
743                     secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
744                             + taskIconHeight);
745                 }
746             }
747         } else {
748             primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
749             // shifts icon half a width left (height is used here since icons are square)
750             primaryIconView.setTranslationX(-(taskIconHeight / 2f));
751             secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
752             secondaryIconView.setTranslationX(taskIconHeight / 2f);
753         }
754         primaryIconView.setTranslationY(0);
755         secondaryIconView.setTranslationY(0);
756 
757         primaryIconView.setLayoutParams(primaryIconParams);
758         secondaryIconView.setLayoutParams(secondaryIconParams);
759     }
760 
761     @Override
getDefaultSplitPosition(DeviceProfile deviceProfile)762     public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
763         if (!deviceProfile.isTablet) {
764             throw new IllegalStateException("Default position available only for large screens");
765         }
766         if (deviceProfile.isLandscape) {
767             return STAGE_POSITION_BOTTOM_OR_RIGHT;
768         } else {
769             return STAGE_POSITION_TOP_OR_LEFT;
770         }
771     }
772 
773     @Override
getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile)774     public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
775             FloatProperty secondary, DeviceProfile deviceProfile) {
776         if (deviceProfile.isLandscape) { // or seascape
777             return new Pair<>(primary, secondary);
778         } else {
779             return new Pair<>(secondary, primary);
780         }
781     }
782 
783     @Override
getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect, @StagePosition int stagePosition, DeviceProfile dp)784     public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
785             @StagePosition int stagePosition, DeviceProfile dp) {
786         if (dp.isLandscape) {
787             float currentTranslationX = floatingTask.getTranslationX();
788             return stagePosition == STAGE_POSITION_TOP_OR_LEFT
789                     ? currentTranslationX - onScreenRect.width()
790                     : currentTranslationX + onScreenRect.width();
791         } else {
792             float currentTranslationY = floatingTask.getTranslationY();
793             return currentTranslationY - onScreenRect.height();
794         }
795     }
796 
797     @Override
setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp)798     public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
799             DeviceProfile dp) {
800         if (dp.isLandscape) {
801             floatingTask.setTranslationX(translation);
802         } else {
803             floatingTask.setTranslationY(translation);
804         }
805 
806     }
807 
808     @Override
getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp)809     public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
810         return dp.isLandscape
811                 ? floatingTask.getTranslationX()
812                 : floatingTask.getTranslationY();
813     }
814 }
815