• 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_VERTICAL;
21 import static android.view.Gravity.END;
22 import static android.view.Gravity.LEFT;
23 import static android.view.Gravity.START;
24 import static android.view.Gravity.TOP;
25 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
26 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
27 
28 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
29 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
30 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
31 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
32 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
33 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
34 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
35 
36 import android.content.res.Resources;
37 import android.graphics.PointF;
38 import android.graphics.Rect;
39 import android.graphics.RectF;
40 import android.graphics.drawable.ShapeDrawable;
41 import android.util.FloatProperty;
42 import android.util.Pair;
43 import android.view.MotionEvent;
44 import android.view.Surface;
45 import android.view.VelocityTracker;
46 import android.view.View;
47 import android.view.accessibility.AccessibilityEvent;
48 import android.widget.FrameLayout;
49 import android.widget.LinearLayout;
50 
51 import com.android.launcher3.DeviceProfile;
52 import com.android.launcher3.R;
53 import com.android.launcher3.Utilities;
54 import com.android.launcher3.util.SplitConfigurationOptions;
55 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
56 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
57 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
58 import com.android.launcher3.views.BaseDragLayer;
59 
60 import java.util.Collections;
61 import java.util.List;
62 
63 public class LandscapePagedViewHandler implements PagedOrientationHandler {
64 
65     @Override
getPrimaryValue(T x, T y)66     public <T> T getPrimaryValue(T x, T y) {
67         return y;
68     }
69 
70     @Override
getSecondaryValue(T x, T y)71     public <T> T getSecondaryValue(T x, T y) {
72         return x;
73     }
74 
75     @Override
getPrimaryValue(int x, int y)76     public int getPrimaryValue(int x, int y) {
77         return y;
78     }
79 
80     @Override
getSecondaryValue(int x, int y)81     public int getSecondaryValue(int x, int y) {
82         return x;
83     }
84 
85     @Override
getPrimaryValue(float x, float y)86     public float getPrimaryValue(float x, float y) {
87         return y;
88     }
89 
90     @Override
getSecondaryValue(float x, float y)91     public float getSecondaryValue(float x, float y) {
92         return x;
93     }
94 
95     @Override
isLayoutNaturalToLauncher()96     public boolean isLayoutNaturalToLauncher() {
97         return false;
98     }
99 
100     @Override
adjustFloatingIconStartVelocity(PointF velocity)101     public void adjustFloatingIconStartVelocity(PointF velocity) {
102         float oldX = velocity.x;
103         float oldY = velocity.y;
104         velocity.set(-oldY, oldX);
105     }
106 
107     @Override
fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile)108     public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
109         // We don't need to check the "top" value here because the startRect is in the orientation
110         // of the app, not of the fixed portrait launcher.
111         if (outStartRect.left > deviceProfile.heightPx) {
112             outStartRect.offsetTo(0, outStartRect.top);
113         } else if (outStartRect.left < -deviceProfile.heightPx) {
114             outStartRect.offsetTo(0, outStartRect.top);
115         }
116     }
117 
118     @Override
setPrimary(T target, Int2DAction<T> action, int param)119     public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
120         action.call(target, 0, param);
121     }
122 
123     @Override
setPrimary(T target, Float2DAction<T> action, float param)124     public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
125         action.call(target, 0, param);
126     }
127 
128     @Override
setSecondary(T target, Float2DAction<T> action, float param)129     public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
130         action.call(target, param, 0);
131     }
132 
133     @Override
set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam)134     public <T> void set(T target, Int2DAction<T> action, int primaryParam,
135             int secondaryParam) {
136         action.call(target, secondaryParam, primaryParam);
137     }
138 
139     @Override
getPrimaryDirection(MotionEvent event, int pointerIndex)140     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
141         return event.getY(pointerIndex);
142     }
143 
144     @Override
getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)145     public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
146         return velocityTracker.getYVelocity(pointerId);
147     }
148 
149     @Override
getMeasuredSize(View view)150     public int getMeasuredSize(View view) {
151         return view.getMeasuredHeight();
152     }
153 
154     @Override
getPrimarySize(View view)155     public int getPrimarySize(View view) {
156         return view.getHeight();
157     }
158 
159     @Override
getPrimarySize(RectF rect)160     public float getPrimarySize(RectF rect) {
161         return rect.height();
162     }
163 
164     @Override
getStart(RectF rect)165     public float getStart(RectF rect) {
166         return rect.top;
167     }
168 
169     @Override
getEnd(RectF rect)170     public float getEnd(RectF rect) {
171         return rect.bottom;
172     }
173 
174     @Override
getClearAllSidePadding(View view, boolean isRtl)175     public int getClearAllSidePadding(View view, boolean isRtl) {
176         return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
177     }
178 
179     @Override
getSecondaryDimension(View view)180     public int getSecondaryDimension(View view) {
181         return view.getWidth();
182     }
183 
184     @Override
getPrimaryViewTranslate()185     public FloatProperty<View> getPrimaryViewTranslate() {
186         return VIEW_TRANSLATE_Y;
187     }
188 
189     @Override
getSecondaryViewTranslate()190     public FloatProperty<View> getSecondaryViewTranslate() {
191         return VIEW_TRANSLATE_X;
192     }
193 
194     @Override
getPrimaryScroll(View view)195     public int getPrimaryScroll(View view) {
196         return view.getScrollY();
197     }
198 
199     @Override
getPrimaryScale(View view)200     public float getPrimaryScale(View view) {
201         return view.getScaleY();
202     }
203 
204     @Override
setMaxScroll(AccessibilityEvent event, int maxScroll)205     public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
206         event.setMaxScrollY(maxScroll);
207     }
208 
209     @Override
getRecentsRtlSetting(Resources resources)210     public boolean getRecentsRtlSetting(Resources resources) {
211         return !Utilities.isRtl(resources);
212     }
213 
214     @Override
getDegreesRotated()215     public float getDegreesRotated() {
216         return 90;
217     }
218 
219     @Override
getRotation()220     public int getRotation() {
221         return Surface.ROTATION_90;
222     }
223 
224     @Override
setPrimaryScale(View view, float scale)225     public void setPrimaryScale(View view, float scale) {
226         view.setScaleY(scale);
227     }
228 
229     @Override
setSecondaryScale(View view, float scale)230     public void setSecondaryScale(View view, float scale) {
231         view.setScaleX(scale);
232     }
233 
234     @Override
getChildStart(View view)235     public int getChildStart(View view) {
236         return view.getTop();
237     }
238 
239     @Override
getCenterForPage(View view, Rect insets)240     public int getCenterForPage(View view, Rect insets) {
241         return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
242             - insets.right - view.getPaddingRight()) / 2;
243     }
244 
245     @Override
getScrollOffsetStart(View view, Rect insets)246     public int getScrollOffsetStart(View view, Rect insets) {
247         return insets.top + view.getPaddingTop();
248     }
249 
250     @Override
getScrollOffsetEnd(View view, Rect insets)251     public int getScrollOffsetEnd(View view, Rect insets) {
252         return view.getHeight() - view.getPaddingBottom() - insets.bottom;
253     }
254 
getSecondaryTranslationDirectionFactor()255     public int getSecondaryTranslationDirectionFactor() {
256         return 1;
257     }
258 
259     @Override
getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)260     public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
261         if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
262             return -1;
263         } else {
264             return 1;
265         }
266     }
267 
268     @Override
getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile, float taskInsetMargin)269     public float getTaskMenuX(float x, View thumbnailView,
270             DeviceProfile deviceProfile, float taskInsetMargin) {
271         return thumbnailView.getMeasuredWidth() + x - taskInsetMargin;
272     }
273 
274     @Override
getTaskMenuY(float y, View thumbnailView, int stagePosition, View taskMenuView, float taskInsetMargin)275     public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
276             View taskMenuView, float taskInsetMargin) {
277         BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
278         int taskMenuWidth = lp.width;
279         if (stagePosition == STAGE_POSITION_UNDEFINED) {
280             return y + taskInsetMargin
281                     + (thumbnailView.getMeasuredHeight() - taskMenuWidth) / 2f;
282         } else {
283             return y + taskInsetMargin;
284         }
285     }
286 
287     @Override
getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile, @StagePosition int stagePosition)288     public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
289             @StagePosition int stagePosition) {
290         if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_UNDEFINED) {
291             return thumbnailView.getMeasuredWidth();
292         } else {
293             return thumbnailView.getMeasuredHeight();
294         }
295     }
296 
297     @Override
setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable)298     public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
299             LinearLayout taskMenuLayout, int dividerSpacing,
300             ShapeDrawable dividerDrawable) {
301         taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
302         dividerDrawable.setIntrinsicHeight(dividerSpacing);
303         taskMenuLayout.setDividerDrawable(dividerDrawable);
304     }
305 
306     @Override
setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile)307     public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
308             LinearLayout viewGroup, DeviceProfile deviceProfile) {
309         // Phone fake landscape
310         viewGroup.setOrientation(LinearLayout.HORIZONTAL);
311         lp.width = MATCH_PARENT;
312         lp.height = WRAP_CONTENT;
313     }
314 
315     @Override
getDwbLayoutTranslations(int taskViewWidth, int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)316     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
317             int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
318             View[] thumbnailViews, int desiredTaskId, View banner) {
319         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
320         float translationX = 0;
321         float translationY = 0;
322         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
323         banner.setPivotX(0);
324         banner.setPivotY(0);
325         banner.setRotation(getDegreesRotated());
326         translationX = banner.getHeight();
327         FrameLayout.LayoutParams snapshotParams =
328                 (FrameLayout.LayoutParams) thumbnailViews[0]
329                         .getLayoutParams();
330         bannerParams.gravity = TOP | (isRtl ? END : START);
331         if (splitBounds == null) {
332             // Single, fullscreen case
333             bannerParams.width = taskViewHeight - snapshotParams.topMargin;
334             return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue());
335         }
336 
337         // Set correct width
338         if (desiredTaskId == splitBounds.leftTopTaskId) {
339             bannerParams.width = thumbnailViews[0].getMeasuredHeight();
340         } else {
341             bannerParams.width = thumbnailViews[1].getMeasuredHeight();
342         }
343 
344         // Set translations
345         if (desiredTaskId == splitBounds.rightBottomTaskId) {
346             float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
347                     ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
348                     : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
349             translationY = snapshotParams.topMargin
350                     + ((taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent);
351         }
352         if (desiredTaskId == splitBounds.leftTopTaskId) {
353             translationY = snapshotParams.topMargin;
354         }
355         return new Pair<>(translationX, translationY);
356     }
357 
358     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
359 
360     @Override
getUpDownSwipeDirection()361     public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
362         return HORIZONTAL;
363     }
364 
365     @Override
getUpDirection(boolean isRtl)366     public int getUpDirection(boolean isRtl) {
367         return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE
368                 : SingleAxisSwipeDetector.DIRECTION_POSITIVE;
369     }
370 
371     @Override
isGoingUp(float displacement, boolean isRtl)372     public boolean isGoingUp(float displacement, boolean isRtl) {
373         return isRtl ? displacement < 0 : displacement > 0;
374     }
375 
376     @Override
getTaskDragDisplacementFactor(boolean isRtl)377     public int getTaskDragDisplacementFactor(boolean isRtl) {
378         return isRtl ? 1 : -1;
379     }
380 
381     /* -------------------- */
382 
383     @Override
getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)384     public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
385         boolean layoutChild) {
386         final int childHeight = child.getMeasuredHeight();
387         final int childBottom = childStart + childHeight;
388         final int childWidth = child.getMeasuredWidth();
389         final int childLeft = pageCenter - childWidth/ 2;
390         if (layoutChild) {
391             child.layout(childLeft, childStart, childLeft + childWidth, childBottom);
392         }
393         return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
394     }
395 
396     @SuppressWarnings("SuspiciousNameCombination")
397     @Override
getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)398     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
399         return rect.left;
400     }
401 
402     @Override
getSplitPositionOptions(DeviceProfile dp)403     public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
404         // Add "left" side of phone which is actually the top
405         return Collections.singletonList(new SplitPositionOption(
406                 R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
407                 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
408     }
409 
410     @Override
getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset, DeviceProfile dp, @StagePosition int stagePosition, Rect out)411     public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
412             DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
413         // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
414         // which is the same bounds as 0 rotation.
415         int width = dp.widthPx;
416         int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp);
417         out.set(0, 0, width, placeholderHeight + insetSizeAdjustment);
418         out.inset(placeholderInset, 0);
419 
420         // Adjust the top to account for content off screen. This will help to animate the view in
421         // with rounded corners.
422         int screenWidth = dp.widthPx;
423         int screenHeight = dp.heightPx;
424         int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
425                 / screenWidth);
426         out.top -= (totalHeight - placeholderHeight);
427     }
428 
429     @Override
updateSplitIconParams(View out, float onScreenRectCenterX, float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY, int drawableWidth, int drawableHeight, DeviceProfile dp, @StagePosition int stagePosition)430     public void updateSplitIconParams(View out, float onScreenRectCenterX,
431             float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
432             int drawableWidth, int drawableHeight, DeviceProfile dp,
433             @StagePosition int stagePosition) {
434         float insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f;
435         out.setX(onScreenRectCenterX / fullscreenScaleX
436                 - 1.0f * drawableWidth / 2);
437         out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
438                 - 1.0f * drawableHeight / 2);
439     }
440 
441     /**
442      * The split placeholder comes with a default inset to buffer the icon from the top of the
443      * screen. But if the device already has a large inset (from cutouts etc), use that instead.
444      */
getPlaceholderSizeAdjustment(DeviceProfile dp)445     private int getPlaceholderSizeAdjustment(DeviceProfile dp) {
446         return Math.max(dp.getInsets().top - dp.splitPlaceholderInset, 0);
447     }
448 
449     @Override
setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, int splitInstructionsWidth)450     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
451             int splitInstructionsWidth) {
452         out.setPivotX(0);
453         out.setPivotY(splitInstructionsHeight);
454         out.setRotation(getDegreesRotated());
455         int distanceToEdge = out.getResources().getDimensionPixelSize(
456                 R.dimen.split_instructions_bottom_margin_phone_landscape);
457         // Adjust for any insets on the left edge
458         int insetCorrectionX = dp.getInsets().left;
459         // Center the view in case of unbalanced insets on top or bottom of screen
460         int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
461         out.setTranslationX(distanceToEdge - insetCorrectionX);
462         out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f)
463                 + insetCorrectionY);
464         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
465         // Setting gravity to LEFT instead of the lint-recommended START because we always want this
466         // view to be screen-left when phone is in landscape, regardless of the RtL setting.
467         lp.gravity = LEFT | CENTER_VERTICAL;
468         out.setLayoutParams(lp);
469     }
470 
471     @Override
getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2)472     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
473             @StagePosition int stagePosition, Rect out1, Rect out2) {
474         // In fake land/seascape, the window bounds are always top and bottom half
475         int screenHeight = dp.heightPx;
476         int screenWidth = dp.widthPx;
477         out1.set(0, 0, screenWidth, screenHeight / 2  - splitDividerSize);
478         out2.set(0, screenHeight / 2  + splitDividerSize, screenWidth, screenHeight);
479     }
480 
481     @Override
setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo, int desiredStagePosition)482     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
483             SplitBounds splitInfo, int desiredStagePosition) {
484         float topLeftTaskPercent = splitInfo.appsStackedVertically
485                 ? splitInfo.topTaskPercent
486                 : splitInfo.leftTaskPercent;
487         float dividerBarPercent = splitInfo.appsStackedVertically
488                 ? splitInfo.dividerHeightPercent
489                 : splitInfo.dividerWidthPercent;
490 
491         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
492             outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
493         } else {
494             outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
495         }
496     }
497 
498     @Override
measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl)499     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
500             int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
501             DeviceProfile dp, boolean isRtl) {
502         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
503         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
504         int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
505                 ? splitBoundsConfig.dividerHeightPercent
506                 : splitBoundsConfig.dividerWidthPercent));
507         int primarySnapshotHeight;
508         int primarySnapshotWidth;
509         int secondarySnapshotHeight;
510         int secondarySnapshotWidth;
511 
512         float taskPercent = splitBoundsConfig.appsStackedVertically ?
513                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
514         primarySnapshotWidth = parentWidth;
515         primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
516 
517         secondarySnapshotWidth = parentWidth;
518         secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
519         secondarySnapshot.setTranslationY(primarySnapshotHeight + spaceAboveSnapshot + dividerBar);
520         primarySnapshot.measure(
521                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
522                 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
523         secondarySnapshot.measure(
524                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
525                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
526                         View.MeasureSpec.EXACTLY));
527     }
528 
529     @Override
setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl)530     public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
531             int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
532         iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
533         iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
534         iconParams.leftMargin = 0;
535         iconParams.topMargin = thumbnailTopMargin / 2;
536         iconParams.bottomMargin = 0;
537     }
538 
539     @Override
setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, DeviceProfile deviceProfile, SplitBounds splitConfig)540     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
541             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
542             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
543             DeviceProfile deviceProfile, SplitBounds splitConfig) {
544         FrameLayout.LayoutParams primaryIconParams =
545                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
546         FrameLayout.LayoutParams secondaryIconParams =
547                 new FrameLayout.LayoutParams(primaryIconParams);
548 
549         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
550         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
551         // It is usually not exactly 50/50, due to insets/screen cutouts.
552         int fullscreenInsetThickness = deviceProfile.getInsets().top
553                 - deviceProfile.getInsets().bottom;
554         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness)
555                 / 2);
556         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
557         float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
558         int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
559         int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
560         int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
561         int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
562 
563         primaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
564         secondaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
565         primaryIconView.setTranslationX(0);
566         secondaryIconView.setTranslationX(0);
567         if (splitConfig.initiatedFromSeascape) {
568             // if the split was initiated from seascape,
569             // the task on the right (secondary) is slightly larger
570             primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
571             secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
572                     + taskIconHeight);
573         } else {
574             // if not,
575             // the task on the left (primary) is slightly larger
576             primaryIconView.setTranslationY(-bottomToMidpointOffset);
577             secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
578         }
579 
580         primaryIconView.setLayoutParams(primaryIconParams);
581         secondaryIconView.setLayoutParams(secondaryIconParams);
582     }
583 
584     @Override
getDefaultSplitPosition(DeviceProfile deviceProfile)585     public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
586         throw new IllegalStateException("Default position not available in fake landscape");
587     }
588 
589     @Override
getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile)590     public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
591             FloatProperty secondary, DeviceProfile deviceProfile) {
592         return new Pair<>(primary, secondary);
593     }
594 
595     @Override
getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect, @StagePosition int stagePosition, DeviceProfile dp)596     public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
597             @StagePosition int stagePosition, DeviceProfile dp) {
598         float currentTranslationY = floatingTask.getTranslationY();
599         return currentTranslationY - onScreenRect.height();
600     }
601 
602     @Override
setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp)603     public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
604             DeviceProfile dp) {
605         floatingTask.setTranslationY(translation);
606     }
607 
608     @Override
getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp)609     public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
610         return floatingTask.getTranslationY();
611     }
612 }
613